I've started to use JHipster weeks ago and everything went find since now. I want to have a LDAP authentification with at the same time the default authentification of JHipster.
I followed this https://jhipster.github.io/tips/016_tip_ldap_authentication.html and it doesn't work as planned.
Actually my configuration is connecting well to my LDAP server and i know by viewing logs that the login search into the LDAP server and compare the password.
The problem is the login fail with the error :
UT005023: Exception handling request to /api/authentication
org.springframework.security.core.userdetails.UsernameNotFoundException: User nseys was not found in the database
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.lambda$onLoginSuccess$1(PersistentTokenRememberMeServices.java:116)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.onLoginSuccess(PersistentTokenRememberMeServices.java:116)
at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.loginSuccess(AbstractRememberMeServices.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
The thing is I want JHipster to automatically create the user in database when it doesn't exist in there with a mapping of parameters (but only when it's a LDAP user) and just connect if it's already done.
I've searched Spring-security solution aswell but the implementations are too far away from the initial files created by JHipster and I don't want to destroy all this.
Well I tried something that work, I don't know if this is how I should have done, but since I've found nothing about that, and it's not documented alot, I'll stick with that solution unless I find a better solution.
// PersistentTokenRememberMeServices.java
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
successfulAuthentication) {
String login = successfulAuthentication.getName();
log.debug("Creating new persistent login for user {}", login);
PersistentToken t = new PersistentToken();
t.setSeries(RandomUtil.generateSeriesData());
t.setTokenValue(RandomUtil.generateTokenData());
t.setTokenDate(LocalDate.now());
t.setIpAddress(request.getRemoteAddr());
t.setUserAgent(request.getHeader("User-Agent"));
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
t.setUser(u);
return t;
}).orElse(null);
if (token == null) {
if (successfulAuthentication.getPrincipal() instanceof LdapUserDetails) {
User ldapUser = new User();
ldapUser.setLogin(login);
ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
ldapUser.setActivated(true);
CustomLdapUserDetails customLdapUserDetails = (CustomLdapUserDetails) successfulAuthentication.getPrincipal();
ldapUser.setEmail(customLdapUserDetails.getEmail());
ldapUser.setFirstName(customLdapUserDetails.getFirstName());
ldapUser.setLastName(customLdapUserDetails.getLastName());
Set<Authority> authorities = new HashSet<>();
authorities.add(this.authorityRepository.findOneByName("ROLE_USER"));
ldapUser.setAuthorities(authorities);
ldapUser.setLangKey("fr");
userRepository.save(ldapUser);
t.setUser(ldapUser);
token = t;
} else {
throw new UsernameNotFoundException("User " + login + " was not found in the database");
}
}
...
}
And I added a contextMapper to get the attributes in the LDAP server
// SecurityConfiguration.java
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return new CustomLdapUserDetails((LdapUserDetails) details, ctx);
}
};
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getLdap().getUrl());
contextSource.setBase(applicationProperties.getLdap().getBase());
contextSource.setUserDn(applicationProperties.getLdap().getUserDn());
contextSource.setPassword(applicationProperties.getLdap().getPassword());
contextSource.afterPropertiesSet(); //needed otherwise you will have a NullPointerException in spring
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userSearchBase(applicationProperties.getLdap().getSearchBase()) //don't add the base
.userSearchFilter(applicationProperties.getLdap().getSearchFilter())
.contextSource(contextSource)
;
}
Related
I have a keycloak instance which I use for CRUD operations with roles. Is there any way to get role permissions? I ve tried to search eveerything about it, but I cant find how to get permissions assigned to a role...
Here is an example of my code:
#RestController
#RequestMapping("/roles")
public class RolesController {
// must use "master" realm and "admin-cli" to connect to the instance
// although other realms and clients can be modified
private Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8437/auth")
.realm("master")
.clientId("admin-cli")
.username("admin")
.password("admin")
.build();
#GetMapping
public ResponseEntity<List<RoleRepresentation>> getRoles() throws IOException {
return new ResponseEntity<>(keycloak.realm("dashing-data").roles().list(), HttpStatus.OK);
}
#PostMapping
public ResponseEntity<RoleRepresentation> createRole(#RequestBody RoleRepresentation role) throws IOException {
List<RoleRepresentation> roleList = keycloak.realm("dashing-data").roles().list();
boolean roleAlreadyExist = roleList.stream().anyMatch(r -> r.getName().contains(role.getName()));
RoleRepresentation newRole = new RoleRepresentation();
if (!roleAlreadyExist){
newRole.setName(role.getName());
newRole.setDescription(role.getDescription());
keycloak.realm("dashing-data").roles().create(newRole);
}
return new ResponseEntity<>(newRole, HttpStatus.OK);
}
#DeleteMapping("/{id}")
public ResponseEntity<String> deleteRole(#PathVariable String id){
RoleByIdResource role = keycloak.realm("dashing-data").rolesById();
if (role == null){
return new ResponseEntity<>("Could not find the role!", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>("Role successfully deleted!", HttpStatus.OK);
}
}
I think you are adding complexity where you don't need it. A keycloak Role should be enough for what you are looking for. If you have a CRUD operation that only managers can do (like a DELETE) you simply create a Role in keycloak called "manager" and assign it to the user that you want. In your backend application, you just have to compare the role name to the required role. On the other hand for a Read operation, you can match if the user has the manager or read Role from keycloak.
I want to return custom message if user during authentication process is locked or expired. I tried to implement this:
#Service
public class UserDetailsHandler implements UserDetailsService {
#Autowired
private UsersService usersService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final Optional<Users> user = usersService.findByLogin(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User '" + username + "' not found");
}
return user
.map(value -> {
return new User(
value.getLogin(),
value.getEncryptedPassword(),
value.getEnabled(),
hasAccountExpired(value.getExpiredAt()),
hasPasswordExpired(value.getPasswordChangedAt()),
hasAccountLocked(value.getLockedAt()),
Collections.singleton(new SimpleGrantedAuthority(value.getRole().getAuthority()))
);
}).orElseThrow(() -> new UsernameNotFoundException("User with username " + username + " not found"));
}
private boolean hasAccountExpired(LocalDateTime account_expired_at) {
return account_expired_at == null;
}
Full code: GitHub
The question is how to create handlers which return some custom message if the validation returns true value for statuses user locked or user expired?
The best option for you is:
Implement Spring UserDetails in your entity Users.
Check in loadUserByUsername if the user has been locked, etc using Spring AccountStatusUserDetailsChecker class.
Add into your EngineExceptionHandler the required methods to manage those exceptions: LockedException, AccountExpiredException, etc
You will see examples of above points in the following links:
Point 1
Point 2
Points 2-3
Well I briefly look at your codes and you implement a JwtTokenFilter that will some how calls the UserDetailsHandler .
In JwtTokenFilter , you already catch and handle EngineException which contain the HTTP status codes and a message. An HTTP response will be sent out which the status and the body message that are the same as what defined in the caught EngineException
It seems that you already configure everything for it to work , so just simply throw EngineException with the suitable HTTP status code and message from the UserDetailsHandler . Something like :
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = usersService.findByLogin(username)
.map(value -> {
return new User(
value.getLogin(),
value.getEncryptedPassword(),
value.getEnabled(),
hasAccountExpired(value.getExpiredAt()),
hasPasswordExpired(value.getPasswordChangedAt()),
hasAccountLocked(value.getLockedAt()),
Collections.singleton(new SimpleGrantedAuthority(value.getRole().getAuthority()))
).orElseThrow(()-> throw new UsernameNotFoundException("User '" + username + "' not found"));
if (user.isAccountLock()){
throw new EngineException(HttpStatus.UNAUTHORIZED , "Custom message for account lock ......")
}
if(user.isAccountExpired()){
throw new EngineException(HttpStatus.UNAUTHORIZED , "Custom message for account expired... ......")
}
}
Spring Security uses the messages.properties which consist of default messages, we can add our custom message with the same. Add messages.properties and add a message as shown below.
AccountStatusUserDetailsChecker.expired=User account has expired
AccountStatusUserDetailsChecker.locked=User account is locked
AbstractUserDetailsAuthenticationProvider.expired=User account has expired
AbstractUserDetailsAuthenticationProvider.locked=User account is locked
You may find the default messages here
Its simple 2 steps approach. User expired means token expired
Step 1
Modify JWTTokenProvider Class to add a custom header to Http Servlet Request using setAttribute() method.
JwtTokenProvider.java
public boolean validateToken(String token,HttpServletRequest httpServletRequest){
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
}catch (SignatureException ex){
System.out.println("Invalid JWT Signature");
}catch (MalformedJwtException ex){
System.out.println("Invalid JWT token");
}catch (ExpiredJwtException ex){
System.out.println("Expired JWT token");
httpServletRequest.setAttribute("expired",ex.getMessage());
}catch (UnsupportedJwtException ex){
System.out.println("Unsupported JWT exception");
}catch (IllegalArgumentException ex){
System.out.println("Jwt claims string is empty");
}
return false;
}
Step 2
Modify commence method in JwtAuthenticationEntryPoint.class to check expired header in http servlet request header that we added in step 1.
JwtAuthenticationEntryPoint.java
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
final String expired = (String) httpServletRequest.getAttribute("expired");
System.out.println(expired);
if (expired!=null){
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
}else{
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
}
}
It is a best practice to return detailed error messages in any REST API. We used this to customize spring rest jwt token expired response to return more detailed error response. We can use this method not only for token expired but also for other jwt token exceptions like SignatureException, Malformed JwtException, UnsupportedJwtException and IllegalArgumentException.
Spring Security 4.2.4. Java 8
I need to change the permissions of users without re-logging them. It's my service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new
SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
But there is a trouble. I need to change roles for any user. Let's say I'm manager and I want to change roles for test user. I need to reload the permissions after saving configuration and test user will have new roles when he press F5 button and reload the page without re-login.
But SecurityContextHolder.getContext().getAuthentication(); returns authentication object only for current user (manager). I need to get authentication object for changed user to call new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
Can I get authentication object for any user or can I solve this problem in another way? Maybe I can get authentication object uses session registry?
P.S.: Doing an expired session for user isn't a good option.
Excellent answer, thanks #virkom!
For newer users, the steps are as follow:
Create a list of authorities you want the user to have (including the previous one(s) they had). In this case (below) I created a set so they would be unique.
Create an authentication object which matches the user you want to authenticate with their new authorities. In this case, I used a user who was in memory to simplify the example, but database authentication is preferred!
Update the security context with the new authentication information. This will have the effect of a refresh of the user's credentials.
A code snippet is shown below:
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("USER"));
authorities.add(new SimpleGrantedAuthority("ADMIN"));
Authentication reAuth = new UsernamePasswordAuthenticationToken("user",new
BCryptPasswordEncoder().encode("password"),authorities);
SecurityContextHolder.getContext().setAuthentication(reAuth);
Sample code is available on github: https://github.com/aoa4eva/ContextDemo
The solution is a bit easier than I thought. I have a user.
And I can call UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), actualAuthorities); instead of UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
I don't need to get authentication object. Final version of service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
I have configured the server.xml file in a Java Spring application to authenticate users when logging in from database tables and roles. I'm wondering how in Java code can I check who's logged in to the application?
I know that in a jsp file I can just use this following syntax to show the name:
${pageContext.request.userPrincipal.name} .
In your Spring MVC Controller, just add the following statement:
String loggedUser = request.getUserPrincipal().getName();
where request is the object of HttpRequest type, made available to you by Spring on demand.
There is very beautiful article for this is given at http://www.baeldung.com/spring-security-track-logged-in-users
You can leverage the HttpSessionBindingListener to update the list of logged in users whenever user information is added to the session or removed from the session based on user logs into the system or logs out from the system.
It will listen to events of type HttpSessionBindingEvent, which are triggered whenever a value is set or removed, or, in other words, bound or unbound, to the HTTP session.
#Component
public class LoggedUser implements HttpSessionBindingListener {
private String username;
private ActiveUserStore activeUserStore;
public LoggedUser(String username, ActiveUserStore activeUserStore) {
this.username = username;
this.activeUserStore = activeUserStore;
}
public LoggedUser() {}
#Override
public void valueBound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (!users.contains(user.getUsername())) {
users.add(user.getUsername());
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (users.contains(user.getUsername())) {
users.remove(user.getUsername());
}
}
// standard getter and setter
}
You can go through the whole code here
You can also retrieve the current logged in user from Spring security
Go through this artical
Or through Request also
request.getUserPrincipal().getName();
You can write a method to get current logged in user as you might need this various places like below :
public User getCurrentLoggedInUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof User) {
return ((User) principal);
}
}
}
I was wondering what I am doing wrong here to authenticate a user. I have an application where the user goes through several steps to activate their account, and upon doing so I would like to bypass the login form and take them directly to their dashboard.
Here is what my automated login function looks like:
protected void automatedLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
CustomUserDetailsService udService = new CustomUserDetailsService(userDAO, request);
UserDetails uDetails = udService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uDetails, password);
token.setDetails(new WebAuthenticationDetails(request));
DaoAuthenticationProvider authenticator = new DaoAuthenticationProvider();
Authentication authentication = authenticator.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
SecurityContextHolder.getContext().setAuthentication(null);
}
}
I must use the DaoAuthenticationProvider class as my authentication provider. I have verified that I am getting a UserDetails model containing the correct credentials, ID, authority roles, etc.
When it calls the authenticate method I run into a Null Pointer somewhere along the way in the DaoAuthenticationProvider class:
org.springframework.security.authentication.AuthenticationServiceException
at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:109)
at
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:132)
at
com.bosch.actions.BaseController.doAutoLogin(BaseController.java:659)
. . . Caused by: java.lang.NullPointerException at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:101)
I'm really not sure what is null, as I don't have the source code available.
Edit
I was able to find the source code here - https://github.com/SpringSource/spring-security/blob/master/core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java
I was able to get around the Null Pointer by explicitly setting the UserDetailsService on the object:
authenticator.setUserDetailsService(udService);
But now I get bad credentials exception when I know the password provided is correct, because I've seen it in the debugger in the UserDetails object set earlier in the code.
org.springframework.security.authentication.BadCredentialsException:
Bad credentials at
org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:87)
at
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:149)
I was able to get the authentication working by piecing together all of the properties defined in the spring bean definition and setting them programmatically on the DaoAuthenticationProvider object. Looking back this seems like it may have been a silly question, but I hope it helps someone!
Corrected Code:
protected void automatedLogin(String username, String password, HttpServletRequest request) {
try {
// Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
CustomUserDetailsService udService = new CustomUserDetailsService(userDAO, request);
CustomMd5PasswordEncoder passEncoder = new CustomMd5PasswordEncoder();
ReflectionSaltSource saltSource = new ReflectionSaltSource();
saltSource.setUserPropertyToUse("salt");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
DaoAuthenticationProvider authenticator = new DaoAuthenticationProvider();
authenticator.setUserDetailsService(udService);
authenticator.setPasswordEncoder(passEncoder);
authenticator.setSaltSource(saltSource);
Authentication authentication = authenticator.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
SecurityContextHolder.getContext().setAuthentication(null);
}
}