Home/JWT + Spring Boot

JWT + Spring Boot

Published On: 27 December 2022.By .
  • General

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.

@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.

@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.

@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.

@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.

@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

We Love Conversations

Say Hello
Go to Top