Learn Spring Security
Contents
Persist Token
As we mentioned in the last chapter we have to associate the generated token with the user by storing it in database to make it available for verification on subsequent requests. We will store these tokens along with the user record in the database.
Let's modify our AppUser entity to store the token and it's expiry time like below:
public class AppUser {
// Other fields omitted for brevity
private String token;
private Date tokenExpiryTime;
}
Our next step is to identify the AppUser record by username and update it with the generated token and it's expiry time. We will do this in UserService component by implementing below updateToken() method.
public void updateToken(String username, String token, Date tokenExpiryTime) {
AppUser appUser = this.get(username);
appUser.setToken(token);
appUser.setTokenExpiryTime(tokenExpiryTime);
appUserRepository.save(appUser);
}
Now we can call the above method from AuthenticationService immediately after generating the token on successful authentication.
if (authentication.isAuthenticated()) {
token = UUID.randomUUID().toString();
userService.updateToken(authentication.getName(), token, this.getTokenExpiryTime());
}
Restart the application to automatically update the APP_USER table with the newly added two columns. Test the GenerateToken API to see the generated token persisted for the given user in the APP_USER table.
Secure update token service
Are we done with persisting the token? Not Completely. We should never let anyone call the updateToken() in UserService anonymously. We have to secure every service method as part of Service Layer Security. In this case we want only the user who requested the token to update his record with the generated token.
Let's define the below condition as a constant in Authority class, where #username represents one of the arguments with the same name defined in updateToken() method, and authentication represents the current authenticated user provided by Spring Security from SecurityContext.
public static final String UPDATE_TOKEN = "#username == authentication.name";
Let's add the above condition to be evaluated before the execution of updateToken() using @PreAuthroize annotation.
@PreAuthorize(Authority.UPDATE_TOKEN)
public void updateToken(String username, String token, Date tokenExpiryTime) {
// Details omitted for brevity
}
As we are authenticating the user using AuthenticationManager by ourselves, SecurityContext will not be aware of the Authentication object returned by the authenticate() method. Unlike Basic Auth, where everything was done out of the box by Spring Security for us, here we have to explicitly set the authenticated Authentication object in SecurityContext.
Set authentication in SecurityContext
Let's define another method in AuthenticationFacade to set the SecurityContext with the authenticated Authentication object
@Component
public class AuthenticationFacade {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public void setAuthentication(Authentication authentication) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
Lastly we will call the above setAuthentication() from AuthenticationService immediately before calling the updateToken() in UserService. The final generateToken() thus looks like below:
public String generateToken(String username, String password) {
Authentication authentication = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
authentication = authenticationManager.authenticate(authentication);
String token = null;
if (authentication.isAuthenticated()) {
token = UUID.randomUUID().toString();
authenticationFacade.setAuthentication(authentication);
userService.updateToken(authentication.getName(), token, this.getTokenExpiryTime());
}
return token;
}
public Date getTokenExpiryTime() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MINUTE, 30);
return calendar.getTime();
}