Spring Security Guide: Authentication with JWT Tokens

Nov 24, 2024

🔐 Spring Security: JWT Authentication Guide

JWT (JSON Web Token) is a stateless token format widely used for authentication and authorization in modern Java applications. Unlike opaque tokens, JWT contains all necessary user information (claims) and a signature, allowing the server to validate requests without storing session state.

This guide shows a clean implementation using Spring Security for developers who want a practical approach.


🔍 JWT Authentication Flow

  1. Login: Client sends username/password to /login.
  2. Token Issuance: Server validates credentials and returns a JWT.
  3. Authenticated Requests: Client includes JWT in Authorization: Bearer <token> header.
  4. Token Validation: Server validates the token, extracts user info, and sets the Authentication in SecurityContext.
Client              Server
   |                   |
   | POST /login       |
   |-----------------> |
   |  username/pass    |
   | <---------------- |
   |   JWT token       |
   |                   |
   | GET /api/resource |
   |-----------------> |
   | Authorization:    |
   | Bearer <JWT>      |
   | <---------------- |
   | 200 OK / Data     |

🛠️ Example Spring Security Configuration with JWT

JWT Service

@Service
public class JwtService {
    private static final String ROLES_CLAIM = "roles";
    private final Algorithm signingAlgorithm;

    public JwtService(@Value("${jwt.signing-secret}") String signingSecret) {
        this.signingAlgorithm = Algorithm.HMAC256(signingSecret);
    }

    public AuthUser resolveJwtToken(String token) {
        DecodedJWT decodedJWT = JWT.require(signingAlgorithm).build().verify(token);
        String userId = decodedJWT.getSubject();
        List<Role> roles = decodedJWT.getClaim(ROLES_CLAIM).asList(Role.class);
        return new AuthUser(userId, roles);
    }

    public String createJwtToken(AuthUser authUser) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        Date exp = new Date(nowMillis + 3600000); // 1 hour validity

        List<String> roles = authUser.roles().stream().map(Role::name).toList();

        return JWT.create()
                .withSubject(authUser.id())
                .withClaim(ROLES_CLAIM, roles)
                .withIssuedAt(now)
                .withExpiresAt(exp)
                .sign(signingAlgorithm);
    }
}

JWT Authentication Filter

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtService jwtService;

    public JwtAuthenticationFilter(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            AuthUser authUser = jwtService.resolveJwtToken(token);
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authUser, null, authUser.roles());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }
}

Security Configuration

@EnableWebSecurity
@Configuration
public class SecurityConfig {
    private final JwtService jwtService;

    public SecurityConfig(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(
                new JwtAuthenticationFilter(jwtService),
                UsernamePasswordAuthenticationFilter.class
            );
        return http.build();
    }
}

✅ Best Practices

  • Use HTTPS to protect token transmission.
  • Keep JWT payload minimal; avoid sensitive data.
  • Set reasonable expiration times; consider refresh tokens.
  • Stateless JWT works well for microservices.
  • For revocation or blacklisting, implement an additional token store.

🧩 Example Requests

Login Request:

POST /login
Body: { "username": "user", "password": "123" }

Login Response:

{ "token": "eyJhbGciOiJIUzI1NiIs..." }

Authenticated Request:

GET /api/user
Header: Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Response:

200 OK
{ "id": "user", "roles": ["USER"] }

🚀 Summary

This guide demonstrates a practical JWT-based authentication implementation in Spring Security. Its stateless design simplifies scaling and microservices integration while maintaining secure access control.

ilia-kritiuk.dev