The issue I have is after the user is authenticated meaning user has signed in, I understand from the client side to logout a user, I delete the token from the local storage but the issue I have is how do I invalidate the token or logout from the serverside.
My intial approach was to make the logout API
permit all in my SecurityFilterChain
but when I try to grab the authenticated user from SecurityContextHolder
after the user had signed in I was getting anonymousUser
.
My second/current approach is I instead authorized LOGOUT API
which means to access the API, a token has to passed in the header. Then I can then set SecurityContextHolder.getContext().setAuthentication(authentication == false);
and also clearContext()
. With this approach I am able to get the logged in user but my questions are:
- Is this the right logic to implement a log out?
- I understand a token cannot be invalidated because it is
STATELESS
. But is there a way to get around this? Because even after setting Authentication to false inSecurityContextHolder
and clearing security contextSecurityContextHolder.clearContext();
when I try accessing AuthenticatedAPI
i.eCRUD operations
, I am still able to use the token.
Here is my login and logout methods in my RestController Class
logout
@PostMapping(path = "/logout", headers = "Authorization")
@ResponseStatus(OK)
public ResponseEntity<?> logout() {
LOGGER.info("Trying to Logout ");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
LOGGER.info("Username {} ", username);
authentication.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.clearContext();
return ResponseEntity.ok().body("Successfully logged out");
}
login
@PostMapping(path = "/login", consumes = "application/json", produces = "application/json")
@ResponseStatus(OK)
public ResponseEntity<?> login(@Valid @RequestBody UserDTO userDTO) {
Authentication authentication;
LOGGER.info("Authenticating {}", userDTO.getUsername());
var authenticationToken = confirmUser(userDTO); // returns a UsernamePasswordAuthenticationToken
try {
authentication = authenticationManager.authenticate(authenticationToken); // Authenticate user password token
SecurityContextHolder.getContext().setAuthentication(authentication); // Set the security context to the logged user
} catch (AuthenticationException e) {
LOGGER.error("Stack trace {}", e.getMessage());
SecurityContextHolder.getContext().setAuthentication(null);
throw new InvalidPasswordException("Wrong username or password");
}
LOGGER.info("{} has signed in", userDTO.getUsername());
return ResponseEntity.ok()
.header( AUTHORIZATION, tokenService.generateToken(authentication) )
.build();
}
CodePudding user response:
I might recommend a different approach, but let's start with your question.
Expiring Access Tokens
To expire a resource server token, you will need to add some kind of state.
This usually comes in the form of some kind of list of valid tokens. If the token isn't in the list, then the token is not valid.
A common way to achieve this is to rely on the authorization server. Many authorization servers ship with an endpoint that you can hit to see if a token is still valid.
Modeling Things Differently
That said, it might be worth considering if you should be thinking about the access token differently. The access token does not represent a user's authenticated session. It represents the user granting access to the client to operate on the user's behalf.
So after the user logs out, it still makes quite a bit of sense for the client to have a valid access token so that the user doesn't have to reauthorize the client every time they log in.