JWT Token-Based Authentication

Implementing JWT (JSON Web Token) based authentication for REST APIs involves several steps. Below is a general guide on how to implement JWT-based authentication in a Spring Security-enabled application:

Step 1: Add Dependencies

Include the necessary dependencies in your project. For JWT, you can use libraries like jjwt (Java JWT) for token handling.

                
        <!-- Maven dependency -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>
    
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
                
            

Step 2: Create a JWT Utility Class

Create a utility class to handle JWT creation, validation, and extraction.

                
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;

    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;

    @Component
    public class JwtTokenUtil {

        @Value("${jwt.secret}")
        private String secret;

        @Value("${jwt.expiration}")
        private Long expiration;

        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            return createToken(claims, userDetails.getUsername());
        }

        private String createToken(Map<String, Object> claims, String subject) {
            return Jwts.builder()
                    .setClaims(claims)
                    .setSubject(subject)
                    .setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + expiration))
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }

        public Boolean validateToken(String token, UserDetails userDetails) {
            final String username = extractUsername(token);
            return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
        }

        private Boolean isTokenExpired(String token) {
            final Date expiration = extractExpiration(token);
            return expiration.before(new Date());
        }

        public String extractUsername(String token) {
            return extractClaim(token, Claims::getSubject);
        }

        public Date extractExpiration(String token) {
            return extractClaim(token, Claims::getExpiration);
        }

        public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
            final Claims claims = extractAllClaims(token);
            return claimsResolver.apply(claims);
        }

        private Claims extractAllClaims(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
    }
                
            

Step 3: Configure Spring Security

Update your Spring Security configuration to include JWT-based authentication.

                
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtTokenUtil jwtTokenUtil;

        @Autowired
        private CustomUserDetailsService userDetailsService;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/api/public/**").permitAll()
                    .antMatchers("/api/private/**").authenticated()
                    .and()
                .formLogin().disable()
                .httpBasic().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtTokenUtil))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), userDetailsService));
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }

        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
                
            

Step 4: Implement JWT Filters

Create filters to handle token-based authentication and authorization.

=> UsernamePasswordAuthenticationFilter
                
    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

        private final AuthenticationManager authenticationManager;
        private final JwtTokenUtil jwtTokenUtil;

        public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenUtil jwtTokenUtil) {
            this.authenticationManager = authenticationManager;
            this.jwtTokenUtil = jwtTokenUtil;
            setFilterProcessesUrl("/api/auth/login");
        }

        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
            // Implement authentication logic, extract username and password from the request
        }

        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
            // Generate and add JWT to the response headers
        }
    }
                
            
=> BasicAuthenticationFilter
                
    public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

        private final JwtTokenUtil jwtTokenUtil;
        private final CustomUserDetailsService userDetailsService;

        public JwtAuthorizationFilter(AuthenticationManager authenticationManager, JwtTokenUtil jwtTokenUtil, CustomUserDetailsService userDetailsService) {
            super(authenticationManager);
            this.jwtTokenUtil = jwtTokenUtil;
            this.userDetailsService = userDetailsService;
        }

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            // Implement authorization logic, validate and set the SecurityContext
        }
    }
                
            

Step 5: Controller Access Control

Annotate your REST controllers to specify access control based on roles or authorities.

                
    @RestController
    @RequestMapping("/api/private")
    public class PrivateController {

        @GetMapping("/data")
        @PreAuthorize("hasRole('ROLE_USER')")
        public ResponseEntity<String> getPrivateData() {
            // Implementation details
        }
    }
                
            

Step 6: Configure Application Properties

Configure your application properties for JWT secret and expiration.

                
    jwt.secret=your_secret_key
    jwt.expiration=3600000
                
            

Note:

  • This is a simplified example. Ensure to handle exceptions, improve error handling, and customize the code based on your application's requirements.
  • Adjust the security configurations, token validity, and secret according to your security policies.

This example demonstrates the basics of JWT-based authentication with Spring Security. Adjustments may be needed based on your specific use case and security requirements.