Spring Security. Special differences - accountNonExpired vs credentialsNonExpired vs accountNonLocked - spring

UserDetails has three parameters accountNonExpired, credentialsNonExpired, and accountNonLocked.
I don't quite understand their differences in action. All three parameters block the account if set to false.
So why are they needed, if they perform the same action, I do not quite understand. After all, you can leave one parameter, for example, accountNonLocked, and that will be enough.
Explain to me please, otherwise I don't understand something.

This is just separate set of reasons to block an account. Each of parameters has it's own exception:
accountNonLocked | LockedException
credentialsNonExpired | CredentialsExpiredException
accountNonExpired | AccountExpiredException
If you write your own exception handler, you could easily customize error messages/code logic. For example:
#PostMapping("/login")
public String login() {
try{
loginService.login();
return "redirect:/main-page";
}
catch (CredentialsExpiredException e) {
return "redirect:/change-credentials-page";
}
catch (LockedException e) {
return "redirect:/write-to-admin-page";
}
... etc
}
Note: this is probably not a proper way to handle auth exceptions nor a good way to handle login logic

Related

Spring-security - invalid login always return 403 instead of appropriate errors

I am trying to input some more "accurate" error handling for invalid logins.
The three main objectives: invalid password, account disabled, invalid email.
The current calling hierarchy is the following:
Attempted login requests
#Override // THIS OVERRIDES THE DEFAULT SPRING SECURITY IMPLEMENTATION
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
return authManager.authenticate(authToken);
}
This calls another override method where I tried to insert error handling because it has access to the userRepo and object. The issue here is if the AccountLockedException or fails on email finding or password verification, it will always reutrn a 403 and no indication of the thrown exception.
#SneakyThrows
#Override // THIS OVERWRITES THE DEFAULT SPRING SECURITY ONE
public UserDetails loadUserByUsername(String email){
User user = findUserByEmail(email);
if ( user != null){
if (user.isEnabled()){
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getName()));});
sucessfulLogin(user);
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
else { throw new AccountLockedException("Account disabled"); }
}
}
However, what I have found this previous method on throwing will call this additional override method (in the same class as the attempted authentication)
#Override // DO SOMETHING WITH THIS TO PREVENT BRUTE FORCE ATTACKS WITH LIMITED NUMBER OF ATTEMPTS IN A TIME-FRAME
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
System.out.println("aaa");
super.unsuccessfulAuthentication(request, response, failed);
}
Though, at this point it will display the following:
this option gets shown when the password is incorrect.
this option gets shown when the account is disabeld.
this option gets shown when the email is incorrect.
My question is. Firstly how do I appropriately distinguish between these errors and secondly send appropriate http responses based on these errors?
if (failed != null) {
if (failed.getMessage() == "AccountLockedException") {
response.setStatus(403);
} // if account is disabled
else if (failed.getMessage() == "EntityNotFoundException") {
response.setStatus(400);
} // if email incorrect
else if (failed.getMessage() == "Bad credentials") {
response.setStatus(400);
} // if password incorrect
else {
System.out.println("some kind of other authentication error");
response.setStatus(418); // some random error incase this ever happens
}

Throwing custom exceptions in multi-layered spring project

I am trying to handle the use case where there's a violation for an unique field (for example, the username/mail), would it be correct to handle it like so? (I am using jdbcInsert on the dao layer)
#Transactional
#Override
public User register(String name, String surname, String username, String email, String password) {
User user = null;
try {
user = userDao.register(name, surname, username,
email, passwordEncoder.encode(password));
} catch (DuplicateKeyException duplicateKeyException) {
throw new DuplicateUserException(duplicateKeyException.getMessage());
} catch (DataAccessException dataAccessException) {
throw new SystemUnavailableException(dataAccessException.getMessage());
}
return user;
}
And catching my custom exceptions in the controller:
#ControllerAdvice
public class ErrorControllerAdvice {
#ExceptionHandler(DuplicateUserException.class)
public ModelAndView keyViolation(DuplicateUserException ex) {
ModelAndView mav = new ModelAndView("admin/user/new");
mav.addObject("duplicateMessage", ex.getErrorMessage());
return mav;
}
#ExceptionHandler(SystemUnavailableException.class)
#ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView unexpectedDatabaseError(SystemUnavailableException ex) {
LOGGER.error(ex.getErrorMessage());
return new ModelAndView("500");
}
}
Looks fine to me. Your custom exceptions live at a different level of abstraction, which gives them good reason for existing.
You might consider handling the exceptions in your controller, instead of using an Error translator class (ErrorControllerAdvice) though. This makes things more explicit and limit surprises about how exceptions are handled.

Get current logged in user from Spring when SessionCreationPolicy.STATELESS is used

I want to implement this example using Keyclock server with Spring Security 5.
I'm going to use OAuth2.0 authentication with JWT token. I'm interested how I can get the current logged in user into the Rest Endpoint?
I have configured Spring Security not to store user sessions using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);.
One possible way is to use this code:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
But I don't know is it going to work. Can someone give some advice for that case?
SecurityContextHolder, SecurityContext and Authentication Objects
By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method
Code:
if (stateless) {
http.setSharedObject(SecurityContextRepository.class,
new NullSecurityContextRepository());
}
if (stateless) {
http.setSharedObject(RequestCache.class, new NullRequestCache());
}
Code:
Method to get user details
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}
private static String extractPrincipal(Authentication authentication) {
if (authentication == null) {
return null;
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
sessionManagement
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You might like to explore Methods with Spring Security to get current user details with SessionCreationPolicy.STATELESS
After the service validate the token, you can parse it, and put it into the securitycontext, it can contains various data, so you have to look after it what you need. For example, subject contains username etc...
SecurityContextHolder.getContext().setAuthentication(userAuthenticationObject);
The SecurityContextHolder's context maintain a ThreadLocal entry, so you can access it on the same thread as you write it in the question.
Note that if you use reactive (webflux) methodology, then you have to put it into the reactive context instead.

Why is the retrieveuser method in DaoAuthenticationProvider calling passwordencoder in UsernameNotFoundException catch?

I looked at the source for DaoAuthenticationProvider and I found something that looked strange to me, in the retrieveUser method.
If the userDetailService throws a UserNameNotFoundException, the following code is in the catch:
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
I fail to see why it calls the passwordEncoder.isPasswordValid. It doesn't use it for anything, then throws the exception up in the chain again. From what I know, the passwordencoder doesn't throw any exceptions, it returns a boolean (which, again, doesn't seem to be used for anything).
Anybody have any idea?
See SEC-2056:
Spring Security's DaoAuthenticationProvider authenticates users by utilizing the PasswordEncoder interface to compare the submitted password with the actual password. If a user is not found, the comparison is skipped which, depending on the PasswordEncoder implementation, can result in a significant difference in the amount of time required to attempt to authenticate an actual user versus a user that does not exist. This opens up the possibility of a side channel attack that would enable a malicious user to determine if a username is valid.
[...]
Fix:
DaoAuthenticationProvider now performs PasswordEncoder.isPasswordValid when a user is not found.
See also: SEC-2608

Spring Validator and BindingResult - How to set different HttpStatus Codes?

Dear Spring Community,
I am building my project using Spring. In my API layer, I am leveraging the Validator interface in order to do some custom validation and set an error.
#Override
public void validate(Object obj, Errors e) {
SignUpRequest signUpRequest = (SignUpRequest) obj;
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("user", ErrorCodes.USER_EXIST, "user already exist");
}
}
Now, in my API signature, since I am using the BindingResult object, in the #ControllerAdvice that I have, if the user provides an empty value for an attribute of my DTO object, I wont be able to get into the #ExceptionHandler(MethodArgumentNotValidException.class).
What this means is that, I wont be able to throw an HttpStatus.BAD_REQUEST for any empty value provided.
In the above case of my validator, It wont be a HttpStatus.BAD_REQUEST but rather it will be a HttpStatus.OK. So my problem is that, how do I provide different HttpStatus types based on the errors I am getting from my validator? Also is there a way to have the empty value still get picked up by the #ExceptionHandler(MethodArgumentNotValidException.class) in my #ControllerAdvice and have my other custom validations picked up by the bindingResult?
I hope I am clear on the question. Appreciate any help!
OK, I believe I came up with the solution!
In order to have different HttpStatus being thrown base on the type of error you have, you need to have custom exceptions. Then have your custom exceptions thrown inside your Validator. Your ControllerAdvice should register to pick up the custom exceptions and act upon them accordingly.
For example the Validator should have something like this:
if (!matcher.matches()) {
e.rejectValue("email", ErrorCodes.EMAIL_INVALID, "email address is invalid");
throw new BadRequestException("email address is invalid", e);
}
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("email", ErrorCodes.USER_EXIST, "user already exist");
throw new ValidationException("User Exist", e);
}
And the Controller Advice should have:
#ExceptionHandler(ValidationException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleValidationException(
ValidationException validationException) {
Map<String, Object> result = createErrorResponse(validationException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
}
#ExceptionHandler(BadRequestException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleBadRequestException(
BadRequestException badRequestException) {
Map<String, Object> result = createErrorResponse(badRequestException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.BAD_REQUEST);
}
This way, you can have different HttpStatus returned base on the type of error you have.

Resources