AntMor
AntMor

Reputation: 447

Spring Boot + JWT Oauth2: Spring 5 vs Spring <5

We have a project where we have:

+------------------------------------+
|                                    |    1. gets the token
|  Authorization Server (Auth)       | <------------------+
|   - spring-security-oauth2:2.0.14  |                    |
|                                    |                    |
+------------------------------------+                    +
                                                        user
                                                          +
+------------------------------------+                    |
|                                    |  2. uses the token |
|  Resource Server (RS)              |  to access resourcs|
|    - spring-security-oauth2:5.1.0  | <------------------+
|                                    |
+------------------------------------+

We have been working in an environment with no Webflux and everything was working as expected. Worth mentioning that this JWT has the following claims: exp, user_name, authorities, jti, client_id, scope.

All the resources have an extra variable:

@GetMapping("/{id}/car")
public SomeDto someResourceMethod(@PathVariable("id") CarId carId, Principal principal)

How do you think we should approach this? Create a new JwtDecoder? We are using Spring 5.1 and the decoder is NimbusReactiveJwtDecoder.

Upvotes: 2

Views: 3298

Answers (1)

jzheaux
jzheaux

Reputation: 7762

At this point, Reactive Resource Server supports JWT claims according to RFC 7519, which is why you see the behavior change.

Yes, you can create your own decoder, which is probably the least invasive way:

public class CustomDecoder implements ReactiveJwtDecoder {
    private final ReactiveJwtDecoder nimbus;
    
    // ...

    public Mono<Jwt> decode(String token) {
        return this.nimbus.decode(token)
            .map(this::mapJwt);
    }

    private Jwt mapJwt(Jwt jwt) {
        Map<String, Object> claims = jwt.getClaims();
        // ... map claims accordingly
        return new Jwt(...);
    }
}

You may also be able to customize the authentication manager, which is introduced in RC2:

public class CustomReactiveAuthenticationManager
    implements ReactiveAuthenticationManager {
    
    private final ReactiveAuthenticationManager delegate;

    // ...

    public Mono<Authentication> authenticate(Authentication authentication) {
        return this.delegate.authenticate(authentication)
            .map(this::mapAuthentication);
    }

    private Authentication mapAuthentication(Authentication authentication) {
        // ... create a custom authentication where getName does what you need
    }
}

OR, if you are able to do some refactoring of your method signatures, then another option would be to use @AuthenticatedPrincipal:

@GetMapping("/{id}/car")
public SomeDto someResourceMethod(
    @PathVariable("id") CarId carId,
    @AuthenticatedPrincipal Jwt jwt) {

    String name = jwt.getClaims().get("user_name");
    // ...
}

or perhaps more succinctly

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "getClaims().get('user_name')")
public @interface CurrentUsername {}    

@GetMapping("/{id}/car")
public SomeDto someResourceMethod(
    @PathVariable("id") CarId carId,
    @CurrentUsername String name) {

    // ...
}

You might also think about logging an enhancement on Spring Security to consider making the user attribute name configurable.

EDIT: I updated the EL expression since by default @AuthenticatedPrincipal calls authentication.getPrincipal() first.

UPDATE: In Spring Security 5.4+, the claim that holds the principal name is configurable, like so:

@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtAuthenticationConverter authenticationConverter =
        new JwtAuthenticationConverter();
    authenticationConverter.setPrincipalClaimName("user_name");
    return authenticationConverter;
}

which then allows the OP to use Principal#getName as with the other principal types.

Upvotes: 3

Related Questions