JWT + Spring Boot
- General
JWT + Spring Boot
In this blog, we will discuss how to add the JWT(JSON Web Token) Authentication with Spring Boot to secure our Rest API’s.
What is JWT ?
When two systems exchange data, you can use a JSON Web Token to identify your user without having to send private credentials on every request.
Flow of Request
JWTUserDetailsService :
It implements the UserDetailsService interface of Spring Security and overrides the loadUserByUsername for fetching user details from the database. The Spring Security Authentication Manager calls this method for getting the user details from the database when authenticating the user details provided by the user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Service public class JwtUserDetailsService implements UserDetailsService { @Autowired private AuthorRepository authorRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Author author = authorRepository.findByUsername(username); if(author == null){ throw new DataValidationException("User Not Found"); } return new User(author.getUsername(), author.getPassword(), new ArrayList<>()); } } |
JWTTokenUtil :
It is responsible for creating and validating the token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
@Component public class JwtTokenUtil implements Serializable { @Autowired private JwtUserDetailsService userDetailsService; private String secretKey = "javainuse"; public static final long JWT_TOKEN_VALIDITY = 1 * 60 * 60; //validate token public Boolean validateToken(String token, UserDetails userDetails){ // to validate first match username and then check token expire or not String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } // fetch username from token public String getUsernameFromToken(String token) { return getClaimsFromToken(token, Claims::getSubject); } public Boolean isTokenExpired(String token){ final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } // check whether the token is expired or not private Date getExpirationDateFromToken(String token) { return getClaimsFromToken(token, Claims::getExpiration); } public <T> T getClaimsFromToken(String token, Function<Claims, T> claimsResolver){ final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } public Claims getAllClaimsFromToken(String token) { return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); } //generate token public String generateToken(UserDetails userDetails){ Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } public String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } } |
JWTRequestFilter :
When the client sends the request, it first comes to it. It checks whether it has valid JWT token or not. If it has, then it sets the authentication in the context so the current user is authenticated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
@Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String tokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // if token is present then extract it if(tokenHeader != null && tokenHeader.startsWith("Bearer")){ jwtToken = tokenHeader.substring(7); // fetch username from token try{ username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e){ System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e){ System.out.println("JWT Token is expired"); } }else { logger.warn("JWT Token does not begin with Bearer String"); } // validate the token if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){ UserDetails userDetails = userDetailsService.loadUserByUsername(username); if(jwtTokenUtil.validateToken(jwtToken, userDetails)){ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } filterChain.doFilter(request, response); } } |
AuthenticationController :
It has the Login and Register API. In this we call the JWT Token Util to generate the token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@Service public class GlobalService { @Autowired AuthorRepository authorRepository; @Autowired private PasswordEncoder bcryptEncoder; @Autowired JwtTokenUtil jwtTokenUtil; public LoginResponse loginResponse(LoginRequest loginRequest){ Author author = authorRepository.findByUsername(loginRequest.getUsername()); if(author == null){ throw new DataValidationException("Author not found"); } if(!bcryptEncoder.matches(loginRequest.getPassword(), author.getPassword())){ throw new DataValidationException("Password does not matches"); } Map<String, Object> claims = new HashMap<>(); String token = jwtTokenUtil.doGenerateToken(claims, author.getUsername()); return new LoginResponse(author, token); } public LoginResponse registerationRequest(RegisterationRequest registerationRequest){ Author existingAuthor = authorRepository.findByUsername(registerationRequest.getUsername()); if(existingAuthor != null){ throw new DataValidationException("Author already exists"); } Author author = new Author(); author.setRole(Role_Enum.USER); author.setPassword(bcryptEncoder.encode(registerationRequest.getPassword())); author.setUsername(registerationRequest.getUsername()); authorRepository.save(author); Map<String, Object> claims = new HashMap<>(); String token = jwtTokenUtil.doGenerateToken(claims, author.getUsername()); return new LoginResponse(author, token); } } |
WebSecurityConfig :
In this we can customize the web security and Http security.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtUserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtRequestFilter jwtRequestFilter; private static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; private static final String GLOBAL_API = "/api/global/**"; @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManagerBean(); } @Override public void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .antMatcher("/api/**") .csrf().disable() .exceptionHandling() .authenticationEntryPoint(this.jwtAuthenticationEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(GLOBAL_API).permitAll() .and() .authorizeRequests() .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() .and() .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } } |
In this way we can use the JWT with the Spring Boot and secure our API’s from the unauthorized access.
Related content
Auriga: Leveling Up for Enterprise Growth!
Auriga’s journey began in 2010 crafting products for India’s