Spring Security 6 with Spring Boot 3 + JWT
In continuation to my article Spring security 6 and spring boot 3 , Next introducing JWT token.
To build Spring Security 6 with Spring Boot 3 + JWT auth server , Start from jwt dependencies :
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
Next start building token end points :
@PostMapping("/token")
public ResponseEntity<JwtResponseDTO> authenticateAndGetToken(
@Valid @RequestBody AccessTokenRequest accessTokenRequestDTO,) {
return new ResponseEntity<>(
authenticationService.getAccessToken(accessTokenRequest), HttpStatus.OK);
}
@PostMapping("/refresh_token")
public ResponseEntity<JwtResponseDTO> refreshtoken(
@Valid @RequestBody RefreshTokenRequest request,) {
return new ResponseEntity<>(
authenticationService.getRefreshToken(request ), HttpStatus.OK);
}
Now build the AuthenticationService that generates the tokens:
@Override
public JwtResponseDTO getAccessToken(AccessTokenRequest accessTokenRequestDTO){
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(accessTokenRequest.getUsername(), accessTokenRequest.getPassword()));
return jwtClient.authenticateAndGetToken(accessTokenRequest,authentication);
}
@Override
public JwtResponse getRefreshToken(RefreshTokenRequest request) {
String refreshToken = request.getRefreshToken();
return jwtClient.findByToken(refreshToken)
.map(jwtClient::verifyExpiration)
.map(TokenRefresh::getUser)
.map(user -> jwtClient.getJwtResponseDTO(user.getUsername()))
.orElseThrow(() -> new RefreshTokenException("error)));
}
JWTClient utilty handles this for AuthenticationService for generating jwt tokens
public JwtResponse authenticateAndGetToken(AccessTokenRequest accessTokenRequest, Authentication authentication,){
if(authentication.isAuthenticated()){
return JwtResponse.builder()
.accessToken(generateToken(username))
.tokenType("Bearer")
.refreshToken(createRefreshToken(username).getRefreshToken())
.expiresIn(accessTokenValiditySeconds)
.build();
}
logger.error("Failed to get JWT response");
throw new AccessTokenException("error");
}
private String generateToken(String username){
Map<String, Object> claims = new HashMap<>();
//get custom claims
claims.put(JWTCustomClaim.name.value ,"value");
return createToken(claims, username,accessTokenValiditySeconds);
}
private String createToken(Map<String, Object> claims, String username , long tokenValiditySeconds) {
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenValidityMS))
.signWith(getSignKey(), SignatureAlgorithm.RS256).compact();
}
private Key getSignKey() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new ClassPathResource(keystoreName).getInputStream(), keystorePassword.toCharArray());
return keyStore.getKey(keystoreAlias, keystorePassword.toCharArray());
} catch (Exception e) {
throw new JwtException("Failed to load keystore", e);
}
}
jwt tokens can be cached once genrated. For subsequent logins , cache is checked first .
refresh tokens are genrated and saved to db or cache as well . Users can refresh tokens to get new access token and refresh token.
JwtClient utilty refresh tokens as below :
public TokenRefresh createRefreshToken(String userName) {
Optional<User> optionalUser = userRepository.findByUsername(userName);
if(optionalUser.isPresent()){
refreshTokenRepository.deleteByUser(optionalUser.get());
TokenRefresh refreshToken = TokenRefresh
.builder()
.refreshToken(createToken(new HashMap<>() , userName
,refreshTokenValiditySeconds))
.expiryDate(Instant.now().plusMillis(refreshTokenValiditySeconds*1000))
.user(optionalUser.get())
.build();
refreshToken = refreshTokenRepository.save(refreshToken);
return refreshToken;
}
logger.error("Refresh token not found");
throw new RefreshTokenException("error");
}
At last , don't forget to adjust SecurityConfig and @EnableWebSecurity
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, OAuthLoginSuccessHandler oAuthLoginSuccessHandler) throws Exception {
http
.anonymous(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(requests -> requests.requestMatchers(HttpMethod.POST,"/token" ,"/refresh_token").permitAll())
.authenticationProvider(authenticationProvider())
.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN)))
// Disable "JSESSIONID" cookies
.sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
Enjoy!