Hodl
Hodl

Reputation: 95

How to add custom token authorization for some endpoints in a spring oauth2 resource server

I have this simple oauth2 spring configuration where the JWT gets validated with an issuer uri.

@EnableWebSecurity
class WebSecurityConfiguration {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/**").hasAnyRole("User", "Client")
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(jwtAuthenticationConverter())
        return http.build()
    }

    private fun jwtAuthenticationConverter(): Converter<Jwt?, out AbstractAuthenticationToken?> {
        val jwtConverter = JwtAuthenticationConverter()
        jwtConverter.setJwtGrantedAuthoritiesConverter(KeycloakRealmRoleConverter())
        return jwtConverter
    }
}

Now for 1 endpoint i need custom token validation. I see 2 ways.

  1. Hardcode a token as a secret and check if thats the bearer token for that endpoint
  2. Create a custom login where u get returned a long time token.

Anyways i need a custom token. Lets say i go for solution 1. How can i for an antmatcher have custom check. So that i can check if the token is the secret saved as environment variable.

antMatchers("/api/custom/players").CHECK with System.env("PLAYERS_TOKEN")

Upvotes: 0

Views: 1082

Answers (1)

ch4mp
ch4mp

Reputation: 12694

You did not provide enough details about what your access-control rules should be from the business point of view, so I'll provide more concepts than specific implementation details.

As you are using JWTs, I would say that all the data required for access-control should be stored in the token. This requires you to configure authorization-server (Keycloak) to add relevant data in private claims. In Keycloak this is done with "mappers". Sample in this project (pay attention to Maven dependencies, implemented interfaces and resource files).

What I understand from your need is you have "players" who subscribe for a time limited access

  • create a REST API to expose subscriptions data (GET request which returns what a given player has subscribed to and until when it is valid)
  • declare a "confidential" client in Keycloak to authenticate with client-credentials with a dedicated role to access this data
  • create a Keycloak mapper which queries this endpoint when a user logs in and adds returned value as private claim to access-tokens (using the above declared confidential client with client-credentials flow).

Once you have all of required player data in the access-token, you can provide your own implementation of AbstractAuthenticationToken (using jwtAuthenticationConverter bean as you already do, just returning your own impl instead of JwtAuthenticationToken). An implementation of your own would permit to hide private claims parsing from the rest of the code and help to write "readable" @PreAuthorize security expressions. Sample of such a custom authentication implementation in this other module of the same repo. Sample usage in security expression (username is a @PathVariable):

@PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('GREET')")

Edit

If you want to authenticate trusted clients without the context of a resource-owner ("real user") then this clients should use client-credentials flow to fetch access tokens from Keycloak:

  • declare "confidential" clients in Keycloak (a different one for each client) and activate client credentials for them
  • assign required roles to those clients
  • configure clients to authenticate against authorization-server to fetch access-tokens with client credentials flow and send it as Bearer authorization header with their requests to resource-server as normal.

From resource-server point of view, there will be no difference: all of the requests will be authorized with access-tokens issued by the same authorization-server.

Upvotes: 1

Related Questions