Search code examples
javaspring-bootspring-securityjwtspring-security-oauth2

How to declaratively use authorization with JWT in Spring 5 controller?


There is a simple web service with one endpoint "GET /hello".
It would be good to declaratively describe in the controller that a JWT is expected in order to extract from it some data about the authorized user making the request.

Exploring some open source projects on Github, I see that the @AuthenticationPrincipal annotation is somehow involved in the process. However, none of the tutorials I've managed to find mention such a declarative approach - they mostly show how to create a JWT, not how to deal with one.
I will be grateful if you point out noteworthy examples that I missed.

Obviously the problem is trivial and related to the basic capabilities of Spring Security, but I can't put the puzzle togeher.

Please, help me to find a proper (natural) way to pass JWT into the controller and get data from it.
Could you share a working example with dependencies and a small test showing how to work with JWT in controller?

SpringBoot 2.4.0

import org.springframework.   ???   .Jwt;
import org.springframework.security.core.annotation.AuthenticationPrincipal;


@RestController 
public class MyController {
 
    @GetMapping("hello")
    public Object getRequests(@AuthenticationPrincipal Jwt jwt) {
      
      String name = getPropertyFromJwt(jwt, "name");
      String id = getPropertyFromJwt(jwt, "id");
      
      return Map.of("name", name, "id", id);
    }
}

Solution

  • After diving deeper I've found out that the question consists of the three:

    1. How to parse JWT string into an object?
    2. How to pass the object into a controller method?
    3. What is the standard/default way in sping boot to do that?

    Here are the short answers:

    1. Use any library you like to convert encoded JWT into an object.
      One of them is com.auth0:java-jwt.

    2. Parameter annotated with @AuthenticationPrincipal can be of any type X and it comes from currentSecurityContext.getAuthorization().getPrincipal() where currentSecurityContext is of type org.springframework.security.core.context.SecurityContext.
      In WebFlux configuration it comes from invocation of ServerSecurityContextRepository.load(...) for every request (see implementation below).

    3. I did not manage to find the answer for the #3, but had realized that there are several open source ad-hoc implementations (good and bad) of this concern. After all I've ended up implementing another one just to understand the pitfals.

    Please, find a sample minimal working project here.

    The key points are:

    • A. Configuration using an implementation of ServerSecurityContextRepository

      @EnableWebFluxSecurity
      public class SecurityConfig {
      
        @Bean
        SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
          ServerSecurityContextRepository repo = createContextRepository();
      
          return http
                     .authorizeExchange(e -> e.anyExchange().authenticated())
                     .securityContextRepository(repo)
                     .build();
         }
      }
      
    • B. Create implementation of Authentication interface and a function (or chain of functions) ServerWebExchange -> Authentication that should be applied in ServerSecurityContextRepository.load method and inserted into a SecurityContext (i.e. SecurityContextImpl) of each server request.

    • C. Principal can be any object on your choice. Return it from Authentication.getPrincipal() method of the implemetation created in (B).