What is JWT
-
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
-
JWT, or JSON Web Tokens (RFC 7519), is a standard that is mostly used for securing REST APIs.
-
JWT is best way to communicate securely between client and server
-
JWT follows stateless authentication mechanism
When should you use JSON Web Tokens?
-
Authorization
-
Information Exchange
[Authorization] This is the most common senario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays.
What is the JSON Web Token structure?
How do JSON Web Token work?
Spring Security JWT Overview
JWT - Securing REST API's with JWT Token using Spring Security 6 & Spring Boot 3
1.1 Development Steps
-
Add JWT related Maven dependencies
-
Create JwtAuthenticationEntryPoint
-
Add Jwt properties in application properties file
-
Create JwtTokenProvider-Utility class
-
Create JwtAuthenticationFilter
-
Create JwtAuthResponse DTO
-
Configure JWT in spring Security
-
Change Login/Signin REST API to Return JWT Token
1.2 Implementation
1.2.1 Add JWT related Maven dependencies
<!--JWT related dependencies--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
1.2.2 Create JwtAuthenticationEntryPoint
It handles the exception that is thrown due to unauthorized user trying to access a resource that requires authentication.
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage()); } }
1.2.3 Add Jwt properties in application properties file
use SHA256 online encoder
//javaguides app-jwt-secret=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb //7 days app-jwt-expiration-milliseconds=604800000
1.2.4 Create JwtTokenProvider-Utility class
Generate and validate Jwt token.
@Component public class JwtTokenProvider { @Value("${app-jwt-secret}") private String jwtSecret; @Value("${app-jwt-expiration-milliseconds}") private long jwtExpirationDate; //generate JWT token public String generateToken(Authentication authentication) { String username = authentication.getName(); Date currentDate = new Date(); Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate); String token = Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(expireDate) .signWith(key()) .compact(); return token; } private Key key() { return Keys.hmacShaKeyFor( Decoders.BASE64.decode(jwtSecret) ); } //get username from Jwt token public String getUsername(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(key()) .build() .parseClaimsJwt(token) .getBody(); String username = claims.getSubject(); return username; } //validate Jwt token public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(key()) .build() .parse(token); return true; } catch (ExpiredJwtException e) { throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Expired JWT token"); } catch (MalformedJwtException e) { throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Invalid JWT token"); } catch (UnsupportedJwtException e) { throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Unsupported JWT token"); } catch (IllegalArgumentException e) { throw new BlogAPIException(HttpStatus.BAD_REQUEST,"JWT claims string is empty"); } } }
1.2.5 Create JwtAuthenticationFilter
JwtAuthenticationFilter authenticate the JWT token.
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private JwtTokenProvider jwtTokenProvider; private UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) { this.jwtTokenProvider = jwtTokenProvider; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //get JWT token from http request String token = getTokenFromRequest(request); //validate token if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)){ //get username from token String username = jwtTokenProvider.getUsername(token); //load the user associated with token UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request,response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7,bearerToken.length()); } return null; } }
1.2.6 Create JwtAuthResponse DTO
@Setter @Getter @AllArgsConstructor @NoArgsConstructor public class JWTAuthResponse { private String accessToken; private String tokenType="Bearer "; }
1.2.7 Configure JWT in spring Security
@Configuration @EnableMethodSecurity public class SecurityConfig { private UserDetailsService userDetailsService; private JwtAuthenticationEntryPoint authenticationEntryPoint; private JwtAuthenticationFilter authenticationFilter; public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationEntryPoint authenticationEntryPoint, JwtAuthenticationFilter authenticationFilter) { this.userDetailsService = userDetailsService; this.authenticationEntryPoint = authenticationEntryPoint; this.authenticationFilter = authenticationFilter; } @Bean public static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests((authorize) -> //authorize.anyRequest().authenticated()) authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll() .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() ).exceptionHandling(exception -> exception .authenticationEntryPoint(authenticationEntryPoint) ).sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ); http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
1.2.8 Change Login/Signin REST API to Return JWT Token
ServiceImpl @Override public String login(LoginDto loginDto) { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( loginDto.getUsernameOrEmail(), loginDto.getPassword() )); SecurityContextHolder.getContext().setAuthentication(authentication); String token = jwtTokenProvider.generateToken(authentication); return token; } Controller @PostMapping(value = {"/login", "/signin"}) public ResponseEntity<JWTAuthResponse> login(@RequestBody LoginDto loginDto) { String token = authService.login(loginDto); JWTAuthResponse jwtAuthResponse = new JWTAuthResponse(); jwtAuthResponse.setAccessToken(token); return ResponseEntity.ok(jwtAuthResponse); }