Search code examples
springspring-bootspring-security

How can I verify a claim from my JWT token with reactive spring security?


I have an application for which my users require an access authority to use the application. As such I did the following:

@Bean
fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
    csrf { disable() }
    formLogin { disable() }
    logout { disable() }
    httpBasic { disable() }
    authorizeExchange {
        authorize(anyExchange, hasAuthority("access"))
    }
    oauth2ResourceServer { jwt {} }
}

Now I would like to add an additional check for a specific claim. I want to verify the contents of the resource_access claim from within my token and more specifically the presence of a key there.

Basically what I would like is something like this:

authorize(anyExchange, hasAuthority("access") && hasResourceAccess("my-resource"))

I've been trying to find how I can do this without having to redefine everything and in a reactive-supporting way.

A few options that I've tried but haven't been able to see through:

1/ OAuth2TokenValidator

I have tried creating my own but I'm stuck at being able to set it as a JwtValidator. Looks like I need to define a ReactiveJwtDecoder but it requires an issuer-uri that I don't have access to.

@Bean
fun jwtDecoder(): ReactiveJwtDecoder =
    (ReactiveJwtDecoders.fromIssuerLocation(issuerUri) as NimbusReactiveJwtDecoder).apply {
        setJwtValidator(MyAccessValidator())
    }

2/ ReactiveAuthorizationManager<AuthorizationContext>

I could create my own but then I'm not sure how I can combine both the AuthorityReactiveAuthorizationManager and mine. I guess I could bundle both logics in my own but I'd like a solution where I can reuse what is already existing.

I wanted to use DelegatingReactiveAuthorizationManager but it seems I cannot because it's a ReactiveAuthorizationManager<ServerWebExchange> and not a ReactiveAuthorizationManager<AuthorizationContext>

I could rewrite it but I guess there is something that already exists that I just wasn't able to find.


Solution

  • So, digging a bit more I found out the following simple enough solution.

    I created my own ReactiveAuthorizationManager although considering how I used it I could have done with a simple Function.

    fun hasResourceAccess(resource: String) = ReactiveAuthorizationManager<Any?> { authenticationMono, _ ->
        authenticationMono
            .mapNotNull {
                val jwt = it.principal
                if (jwt is Jwt) jwt.getClaimAsMap("resource_access")
                else null
            }
            .map { AuthorizationDecision(it.containsKey(resource)) }
            .defaultIfEmpty(AuthorizationDecision(false))
    }
    

    An I used it this way

    @Bean
    fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
        csrf { disable() }
        formLogin { disable() }
        logout { disable() }
        httpBasic { disable() }
        authorizeExchange {
            authorize(anyExchange) { authenticationMono, authorizationContext ->
                Flux
                    .merge(
                        hasAuthority("access").check(authenticationMono, authorizationContext),
                        hasResourceAccess("my-resource").check(authenticationMono, authorizationContext)
                    )
                    .all(AuthorizationDecision::isGranted)
                    .map(::AuthorizationDecision)
            }
        }
        oauth2ResourceServer { jwt {} }
    }