1

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);
    }
}
diziaq
  • 6,881
  • 16
  • 54
  • 96
  • everything you need is here: https://www.baeldung.com/get-user-in-spring-security if you want to read the JWT as is(In base64) add HttpServletRequest request as method paramter: (public Object getRequests(, HttpServletRequest request) ) and call request.getHeader("authorization"); then you will have the JWT directly – Roie Beck Jan 24 '21 at 11:32
  • another option to access the header directly(using @RequestHeader annotation): https://stackoverflow.com/a/54911059/3942132 – Roie Beck Jan 24 '21 at 11:40
  • @RoieBeck Ok, we have that header as String. What next, how to parse it? – diziaq Jan 25 '21 at 11:54
  • https://jwt.io/ gives you a list of all the frameworks for java :) , https://github.com/auth0/java-jwt maven package is an example, search for "Decode a Token" in the github page, it is really simple. each library got it's own way to decode a JWT, you can also do Base64.decode to jwt body(second part of the JWT is also called payload) yourself :), as a genreral rule, I think you need to understand JWT better before using it... – Roie Beck Jan 26 '21 at 07:58

2 Answers2

1

To authenticate successfully , the request must be authenticated by a AuthenticationProvider . Once it successfully authenticates , it will return a Authentication object which contain a principal object.

@AuthenticationPrincipal just helps to access this principle object. However, spring-security does not provide a AuthenticationProvider that can authenticate with JWT out of the box, that means you have to implement by yourself. In your customised AuthenticationProvider , you verify the JWT and decode the property that you are interested and create a customised principal object that hold these properties such that you can access them by @AuthenticationPrincipal.

Just have a quick google and found this example may help you.

Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • Thank you Ken Chan. I believe this example can lead me the right way. Generally, don't you find it strange that every app is supposed to implement its own AuthenticationProvider from scratch, although the very area of auth is so standardized. I would expect to see at least a basic implementation of this "JWT concern" in a SpringBoot starter. And it seems to me that for WebFlux there will be additional troubles with the same stuff. – diziaq Feb 05 '21 at 09:06
0

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

diziaq
  • 6,881
  • 16
  • 54
  • 96