Spring Security and JWT

In this post I show the usage of Spring Security with the JWT base authentication. For that, I use the HTTP filters and a service Provider for the creation and validation of the JWT.

Content:

  • The configuration of Spring Security;
  • The HTTP filters;
  • The creation and validation of a JWT.

Check this explanatory video for more details.

All the code of the article is available in the following repository.

In a previous article, I’ve already added some controllers and services. But the are unprotected. Now, I need to configure Spring Security to protect them.

I will start configuring the CORS, with the allowed headers and the HTTP methods.

@Configuration
@EnableWebMvc
public class WebConfig {

    private static final Long MAX_AGE = 3600L;
    private static final int CORS_FILTER_ORDER = -102;

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.setAllowedHeaders(Arrays.asList(
                HttpHeaders.AUTHORIZATION,
                HttpHeaders.CONTENT_TYPE,
                HttpHeaders.ACCEPT));
        config.setAllowedMethods(Arrays.asList(
                HttpMethod.GET.name(),
                HttpMethod.POST.name(),
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name()));
        config.setMaxAge(MAX_AGE);
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));

        // should be set order to -100 because we need to CorsFilter before SpringSecurityFilter
        bean.setOrder(CORS_FILTER_ORDER);
        return bean;
    }
}

The configuration of Spring Security

Let’s continue adding the Maven dependency for Spring Security.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

And now, in the security configuration, i will need the following:

  • An entry point to handle the authentication exceptions, this is not mandatory but i prefer to return a proper message than a stacktrace;
  • An HTTP filter dedicated to handle the regular authentication, the login and password authentication;
  • An HTTP filter dedicated to handle the authentication with JWT;
  • Indicate that the session will never be created, as i am in a stateless application. Which endpoints doesn’t require the authentication, and the remaining will require the authentication;
  • And i will create a component, a provider, which will validate the token and communicate with the authentication service to validate the login and password.
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserAuthenticationEntryPoint userAuthenticationEntryPoint;
    private final UserAuthenticationProvider userAuthenticationProvider;

    public SecurityConfig(UserAuthenticationEntryPoint userAuthenticationEntryPoint,
                          UserAuthenticationProvider userAuthenticationProvider) {
        this.userAuthenticationEntryPoint = userAuthenticationEntryPoint;
        this.userAuthenticationProvider = userAuthenticationProvider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling().authenticationEntryPoint(userAuthenticationEntryPoint)
                .and()
                .addFilterBefore(new UsernamePasswordAuthFilter(userAuthenticationProvider), BasicAuthenticationFilter.class)
                .addFilterBefore(new JwtAuthFilter(userAuthenticationProvider), UsernamePasswordAuthFilter.class)
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/v1/signIn", "/v1/signUp").permitAll()
                .anyRequest().authenticated();
    }

}

Let’s create previously used classes.

The HTTP Filters

Why handle the authentication via HTTP filters?

This way, the authentication is not present in the code, in the controllers. The HTTP filters will be executed before reaching the controls. For the username/password authentication, i could have done in the controller, but for authentication based on a token, i must always pass through a process to validate the request is authenticated. The token must be sent in all the requests done by the frontend. It will be sent in a header. So, before reaching a controller, where it’s asking for some personal content, i must ensure that the request has the authentication information in the headers. If yes, let continue the request until the controller. And if it’s not correctly authenticated, return an exception.

Okay then, why to use an HTTP filter for the username/password authentication?

This way, i have a single way to authenticate a user: via the HTTP filters. I don’t have the logic splitted in several services. But the best advantage is the following.

@PostMapping("/signIn")
public ResponseEntity<UserDto> signIn(@AuthenticationPrincipal UserDto user) {
   user.setToken(userAuthenticationProvider.createToken(user.getLogin()));
   return ResponseEntity.ok(user);
}

The @AuthenticationPrincipal will inject the object of the authenticated user in my controller. Using the HTTP filters, i set into the security context the user information, the user object, and spring, with this annotation, will look for the user object in the security context. This way, having the user authenticated with any system type, JWT or password, i will always have the user object injected in my controllers.

Let’s go now with the filter dedicated to handle the regular authentication, the authentication via username and password.

public class UsernamePasswordAuthFilter extends OncePerRequestFilter {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final UserAuthenticationProvider userAuthenticationProvider;

    public UsernamePasswordAuthFilter(UserAuthenticationProvider userAuthenticationProvider) {
        this.userAuthenticationProvider = userAuthenticationProvider;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            FilterChain filterChain) throws ServletException, IOException {

        if ("/v1/signIn".equals(httpServletRequest.getServletPath())
                && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
            CredentialsDto credentialsDto = MAPPER.readValue(httpServletRequest.getInputStream(), CredentialsDto.class);

            try {
                SecurityContextHolder.getContext().setAuthentication(
                        userAuthenticationProvider.validateCredentials(credentialsDto));
            } catch (RuntimeException e) {
                SecurityContextHolder.clearContext();
                throw e;
            }
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

Let’s go now with the more interesting filter, the JWT filter.

public class JwtAuthFilter extends OncePerRequestFilter {

    private final UserAuthenticationProvider userAuthenticationProvider;

    public JwtAuthFilter(UserAuthenticationProvider userAuthenticationProvider) {
        this.userAuthenticationProvider = userAuthenticationProvider;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            FilterChain filterChain) throws ServletException, IOException {
        String header = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);

        if (header != null) {
            String[] authElements = header.split(" ");

            if (authElements.length == 2
                    && "Bearer".equals(authElements[0])) {
                try {
                    SecurityContextHolder.getContext().setAuthentication(
                            userAuthenticationProvider.validateToken(authElements[1]));
                } catch (RuntimeException e) {
                    SecurityContextHolder.clearContext();
                    throw e;
                }
            }
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

Before going to the authentication provider service, let’s quickly see what the entry point contains, the entry point which handles the authentication exceptions.

@Component
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        OBJECT_MAPPER.writeValue(response.getOutputStream(), new ErrorDto("Unauthorized path"));
    }
}

And now, the expected service provider where the JWT is built and validated, and where the username/password is also validated.

The creation and validation of a JWT

@Component
public class UserAuthenticationProvider {

    @Value("${security.jwt.token.secret-key:secret-key}")
    private String secretKey;

    private final AuthenticationService authenticationService;

    public UserAuthenticationProvider(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    @PostConstruct
    protected void init() {
        // this is to avoid having the raw secret key available in the JVM
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    public String createToken(String login) {
        Claims claims = Jwts.claims().setSubject(login);

        Date now = new Date();
        Date validity = new Date(now.getTime() + 3600000); // 1 hour

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    public Authentication validateToken(String token) {
        String login = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();

        UserDto user = authenticationService.findByLogin(login);

        return new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList());
    }

    public Authentication validateCredentials(CredentialsDto credentialsDto) {
        UserDto user = authenticationService.authenticate(credentialsDto);
        return new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList());
    }


}

The JWT is divided in three parts:

  • The header which contains the information about the algorithm used to cipher the token, and what kind of token it is.
  • Then comes the payload with the claims. The claims are some standard information you can find in the token, as the username, the expiration time, the creation date, and more. And i can have some custom claims.
  • And the last part is the signature. For the signature, i use the header content, encoded in base64, i then use the payload content also encoded in base64, and encrypt the concatenation of both using the algorithm specified in the header. The resulting token will be: the header encoded in base64, followed by a dot, the payload encoded in base64, followed by a dot, and the signature.
DecodedEncoded
{
“alg”: “HS256”,
“typ”: “JWT”
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
“sub”: “1234567890”,
“name”: “John Doe”,
“iat”: 1516239022
}
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
HMACSHA256(
base64UrlEncode(header)
+ “.”
+ base64UrlEncode(payload),
“the-secret-key”)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Hopefully i have a library to perform those actions for me.

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

Conclusion

  • I created the security configuration which defines the order of the HTTP filters, the exception handler with the entry point, the endpoints which don’t need authentication, and the session management;
  • I’ve created two HTTP filters, one for the username/password authentication, and the other for JWT;
  • I’ve created the entry point to handle the authentication exceptions, and return a custom response;
  • I’ve created a service provider which builds the JWT, validates it, and validates the password;
  • And finally, if the request is correctly authenticated, add the user information in the security context to have it available in the controllers via the annotation.

References

All the code of the article is available in the following repository.


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

3 responses to “Spring Security and JWT”

  1. […] created some articles where i’ve implemented multiple ways a request can be authenticated: JWT, cookie or session. The authentication is the action that validates that a user who makes a request […]

    Like

  2. […] authenticate a stateless application with the cookies. And I will show the differences with the JWT […]

    Like

  3. […] a previous article, I’ve configured my application with an authentication system. But I haven’t any […]

    Like

Leave a reply to Cookie based authentication with Spring Security – The Dev World – by Sergio Lema Cancel reply

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading