Spring Boot fetch user role from DB using LDAP login - spring

I could login via LDAP but I cant fetch user role which is stored in my Database. I do get the following error:
org.springframework.security.ldap.userdetails.LdapUserDetailsImpl cannot be cast to com.test.rnd.geo.web.dto.CustomUser
private CustomUser getUserDetails() {
CustomUser userDetails = (CustomUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
LOGGER.info("Deatils: "+userDetails);
LOGGER.info("UserName: " + userDetails.getUsername());
LOGGER.info("Auth Token: " + userDetails.getAuthToken());
LOGGER.info("User Role size: " + userDetails.getAuthorities().size());
LOGGER.info("User Role : " + userDetails.getAuthorities());
return userDetails;
}
I am getting error to fetch this getUserDetails() function.
CustomUser customUser = getUserDetails();
String role = customUser.getAuthorities().stream().findFirst().get().getAuthority();

You do need to use a userDetailsContextMapper() and mapUserFromContext() functions. Where you could authenticate your custom user and then return the CustomUser object.

Related

Error message is not displayed when when validating a user using spring framework

So I am trying to authenticate a user using a POST login API but the issue is when the user does not exist (meaning username (unique) not in the database) the thrown error message is not displayed on the client side (POSTMAN). I tried debugging and the error is thrown but not displayed all I see is Status: 401 Unauthorized from POSTMAN
But when the user exists but the password doesn't match, it displays the correct thrown error message. NOTE: I am using spring's OAuth 2.0 Resource Server JWT
Controller method
#PostMapping(path = "/login", consumes = "application/json")
public ResponseEntity<?> login(#Valid #RequestBody UserDTO userDTO) throws UsernameNotFoundException {
LOGGER.info("Authenticating {}", userDTO.getUsername());
userDTOService.confirmUser(userDTO); // Where the issue occurs
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(userDTO.getUsername(), userDTO.getPassword()));
return ResponseEntity.ok()
.header(
HttpHeaders.AUTHORIZATION,
tokenService.generateToken(authentication)
)
.build();
}
Service method (confirm user method)
public void confirmUser(UserDTO userDTO) throws UsernameNotFoundException {
/*
* Check if username exist in the database
* Check if the password provided equals password in database
* */
String username = userDTO.getUsername();
String password = userDTO.getPassword();
Optional<User> user = userRepository.findUserByUserName(username);
// This error is not displayed
if (user.isEmpty()) {
LOGGER.error("User {} does not exist", username);
throw new UsernameNotFoundException(username + " does not exist");
}
boolean checkingCredentials = user
.stream()
.anyMatch(
param ->
param.getUsername().equals(username)
&&
passwordEncoder.matches(password, param.getPassword())
);
if (!checkingCredentials) {
LOGGER.error("Bad user credentials");
throw new RuntimeException("Please check username or password");
}
}
The reason I was getting a 401 instead of the correct error message is because my approach was wrong. I had 2 solutions to this but I am not sure if the 2nd is the industry standard.
The first approach:
Pass the user credentials to a UsernamePasswordToken to generate a token.
Then I the token into the authentication manager to be authenticated
Surround the auth manager in a try catch block to return an exception. The thrown error message will be of your chosen.
The second approach:
I want to check if user exists in the database or else throw Not found exception
If step 1 passed then I want to check the user password trying to log in and the hashed password in the database. If they do not match, I want to throw an invalid password exception
If no error is thrown, then I want to pass the users name, password and authorities into UsernamePasswordAuthenticationToken().

Get the authority of the user at the time of deleting it in jhipster

I need to get the authority of the user which I am going to delete. My attempt as follows.
#DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
#Timed
#Secured({AuthoritiesConstants.ADMIN, AuthoritiesConstants.LECTURER})
public ResponseEntity<Void> deleteUser(#PathVariable String login) {
log.debug("REST request to delete User: {}", login);
boolean hasAuthorityAdmin = false;
boolean hasAuthorityMember = false;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
hasAuthorityAdmin = authorities.contains(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN));
hasAuthorityMember = authorities.contains(new SimpleGrantedAuthority(AuthoritiesConstants.MEMBER));
if (hasAuthorityAdmin) {
// delete user
userService.deleteUser(login);
} else {
if (hasAuthorityMember) {
// delete user if it is a student
if (**x**.contains(AuthoritiesConstants.STUDENT)) {
userService.deleteUser(login);
}
}
}
return ResponseEntity.ok().headers(HeaderUtil.createAlert("userManagement.deleted", login)).build();
}
There instead of x I need a method to retrieve it? That means I need to retrieve the authority which I am going to delete. So anyone have idea. This is inside the userResource.java. Can anyone help me with the code?
Suppose that I am logged in as an member. Then I am going to delete student. So when I am click the delete button of the student record, should be able to get the ROLE_STUDENT via a method.
This should do it :
if (hasAuthorityMember) {
Optional<User> user = userService.getUserWithAuthoritiesByLogin(login);
Set<Authority> currentUserAuthorities = user.get().getAuthorities();
for(Authority auth : currentUserAuthorities) {
// delete user if it is a student
if(auth.getName().equals(AuthoritiesConstants.STUDENT)) {
userService.deleteUser(login);
}
}
}
With UserService, you get the user and his authorities thanks to its login, and for each of its authorities (if there are many), we check the name of the authority. If corresponds to Student, you then delete the user.

Migrating to keycloak for an app that uses spring security

I'm looking for steps to keycloak for an Spring MVC app that uses spring security currently.
I wanted to use keycloak in Sitewhere.
I guess this is so simple if I would have read keycloak's document fully:). Any how here are the steps that I followed while migrating to keycloak in Sitewhere .
Follow the steps as given in keycloak doc for spring-security
Add the dependency to sitewhere-core & sitewhere-web pom.xml as stated in adapter installation
Also add the jboss-logging dependency in sitewhere-web's pom.xml since, keycloak spring adapter has a hardcode dependency for jboss-logging.
Modify applicationcontext.xml so that it can uses keycloak for both web & api, following the sample for api
<sec:http pattern="/api/**" entry-point-ref="keycloakAuthenticationEntryPoint">
<sec:custom-filter ref="keycloakPreAuthActionsFilter" before="LOGOUT_FILTER" />
<sec:custom-filter ref="keycloakAuthenticationProcessingFilter" before="FORM_LOGIN_FILTER" />
Modify LoginManager.java as follows
public static IUser getCurrentlyLoggedInUser() throws SiteWhereException {
Authentication KeyCloakAuth = SecurityContextHolder.getContext().getAuthentication();
if (KeyCloakAuth == null) {
throw new SiteWhereSystemException(ErrorCode.NotLoggedIn, ErrorLevel.ERROR,
HttpServletResponse.SC_FORBIDDEN);
}
KeycloakAccount keyAccount = ((KeycloakAuthenticationToken) KeyCloakAuth).getAccount();
String username = keyAccount.getKeycloakSecurityContext().getIdToken().getPreferredUsername();
String password = "";
IUser user = SiteWhere.getServer().getUserManagement().authenticate(username, password);
List<IGrantedAuthority> auths =
SiteWhere.getServer().getUserManagement().getGrantedAuthorities(user.getUsername());
SitewhereUserDetails details = new SitewhereUserDetails(user, auths);
Authentication auth = new SitewhereAuthentication(details, password);
if (!(auth instanceof SitewhereAuthentication)) {
throw new SiteWhereException("Authentication was not of expected type: "
+ SitewhereAuthentication.class.getName() + " found " + auth.getClass().getName()
+ " instead.");
}
return (IUser) ((SitewhereAuthentication) auth).getPrincipal();
}
Since, we have migrated our authentication to keycloak and for the fact that we will not get credentials of user in siterwhere it's better to void the code related to password validation in authentication method of IUserManagement. Following is the sample from MongoUserManagement.java
public IUser authenticate(String username, String password) throws SiteWhereException {
if (password == null) {
throw new SiteWhereSystemException(ErrorCode.InvalidPassword, ErrorLevel.ERROR,
HttpServletResponse.SC_BAD_REQUEST);
}
DBObject userObj = assertUser(username);
String inPassword = SiteWherePersistence.encodePassoword(password);
User match = MongoUser.fromDBObject(userObj);
//nullify authentication since we are using keycloak
/*if (!match.getHashedPassword().equals(inPassword)) {
throw new SiteWhereSystemException(ErrorCode.InvalidPassword, ErrorLevel.ERROR,
HttpServletResponse.SC_UNAUTHORIZED);
}*/
// Update last login date.
match.setLastLogin(new Date());
DBObject updated = MongoUser.toDBObject(match);
DBCollection users = getMongoClient().getUsersCollection();
BasicDBObject query = new BasicDBObject(MongoUser.PROP_USERNAME, username);
MongoPersistence.update(users, query, updated);
return match;}
Make sure you have respective roles for the users in keycloak that are more specific to sitewhere.
Change your home page so that it redirects to keycloak for authentication purpose. Following is the sample for redirection:
Tracer.start(TracerCategory.AdminUserInterface, "login", LOGGER);
try {
Map<String, Object> data = new HashMap<String, Object>();
data.put("version", VersionHelper.getVersion());
String keycloakConfig = environment.getProperty("AUTHSERVER_REDIRECTION_URL");
if (SiteWhere.getServer().getLifecycleStatus() == LifecycleStatus.Started) {
return new ModelAndView("redirect:"+keycloakConfig);
} else {
ServerStartupException failure = SiteWhere.getServer().getServerStartupError();
data.put("subsystem", failure.getDescription());
data.put("component", failure.getComponent().getLifecycleError().getMessage());
return new ModelAndView("noserver", data);
}
} finally {
Tracer.stop(LOGGER);
}

How to get Original User after Impersonating another user?

I am using Switch user filter given by spring for Impersonating an user.
How can I get the Original user Who is Impersonating in the SwitchUserFilter.
Steps I am doing:
Ex.
1. Log in with User1
2. Impersonting to the User2. (user1 impersonate User2)
3. In Filter I am getting **authentication.getName()** as **User2**
4. While switching back to Original I am getiing **authentication.getName()** as **Null**
Now My Need is I want to get the original user (User1) in the filter at the time of swtichback.
Can it be possible.
Please suggest.
Let me know anyone needs any more inputs. please comment.
Thanks in advance.
This is how you can access the original User :
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR.equals(grantedAuthority.getAuthority())) {
System.out.println(((SwitchUserGrantedAuthority) grantedAuthority).getSource().getPrincipal());
}
}
add this custom method in UserJwTController in jhipster generated application
#PostMapping("/authenticate-externalnodes")
public ResponseEntity<JWTToken> authenticateExternalnodes(#Valid #RequestBody LoginVM loginVM) {
// Get Roles for user via username
Set<Authority> authorities = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername()).get()
.getAuthorities();
// Create Granted Authority Rules
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Authority authority : authorities) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority.getName()));
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginVM.getUsername(), "", grantedAuthorities);
Authentication authentication = authenticationToken;
SecurityContextHolder.getContext().setAuthentication(authentication);
boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
String jwt = tokenProvider.createToken(authentication, rememberMe);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
}

Spring security : auto login issue

i am trying to auto login user after signup. Here is code for auto login
private boolean autoLogin(HttpServletRequest request, User user) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority("ADMIN");
Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();
authorities.add(auth);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
user.getEmail(), user.getPassword(), authorities);
token.setDetails(new WebAuthenticationDetails(request));
authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(token);
return true;
}
and inside an interceptor that check logged in user code is
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Problem is when i debug the code (after auto login) the principal object has logged in user's email address instead of UserDetails object.
Things working fine when i log in useing spring security login form.
You're missing re-assigning the return from AuthenticationManager.authenticate().
This line:
authenticationManager.authenticate(token);
should be:
token = authenticationManager.authenticate(token);
That should fix things.

Resources