Search code examples
javaspringspring-securityoauth-2.0

Spring Security + OAuth: settings for local JWT validation


I'm building microservices app, secured by Keycloak and want to secure each microservice. It was written here many times, that it's not a good idea to query Auth service (Keycloak - in my case) for every time you want to validate your token. So it's better to validate a signature locally, as soon as your request passes through API-Gateway to the microservices.

So, I want to setup my Spring-app this way.

Am I right stating that:

  1. jwt.jwk-set-uri parameter is responsible for checking JWT keys locally. So when I'm setting up this, open keys will be fetched from Keycloak's endpoint for the future checking of JWT-signature validity offline. Without accessing Keycloak's server per every check.
  2. If I set issuer-uri without jwk-set-uri there will be only on-line JWT-check per every request.

If all of above mentioned is false, please describe the right way in your opinion. Unfortunately, the Spring's doc is not so verbose on this, stating that:

Consequently, Resource Server will not ping the authorization server at startup. We still specify the issuer-uri so that Resource Server still validates the iss claim on incoming JWTs.

The config I'm talking about is here(application.yaml):

  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:3000/realms/msapp
          jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs

SecurityConfig

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity()
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            .csrf().disable()
            .authorizeExchange()
                .pathMatchers("/login").permitAll()
            .anyExchange()
                .authenticated().and()
            .oauth2ResourceServer() 
                .jwt()
                .jwtAuthenticationConverter(keykloakJwtAuthConverter());
        return http.build();
    }

Solution

  • When used alone, issuer-uri serves two purposes:

    • discover OpenID configuration (which usually stands at {issuer-uri}/.well-known/openid-configuration), read the JWKS endpoint URI from it, and then fetch public keys
    • setup an issuer validator in the JWT decoder: if the value of the iss claim in the token does not match exactly what is configured, an exception is thrown (even trailing slash, if any, is important).

    When jwk-set-uri is provided, public keys are fetched using it, whatever is configured as issuer-uri (which can be omitted, in which case the JWT decoder won't validate the issuer). But tokens are still validated with a JWT decoder, so you are wrong when thinking that a round-trip to the authorization server is made for every request when you set jwk-set-uri. This round-trip happens when you use access token introspection (instead of JWT decoding) or if you explicitly wire it in the authentication converter or manager (for instance use a REST client to call Keycloak's authorization service).

    In my opinion, you should validate issuer, and therefore, provide with issuer-uri. In that case, you need to provide a jwk-set-uri only if, because of your network configuration, the resource server can't reach the authorization server using what it sets as audience claim (this happens when authorization server is misconfigured and puts a localhost URI as audience in a containerized environment). So, in the case of your configuration (where you reuse issuer-uri to define jwk-set-uri), just remove the jwk-set-uri, it will be inferred from OpenID configuration.

    P.S.

    As already written in another question, you would really save time and efforts reading my tutorials: your resource server config still has a reference to /login (which it should not), CSRF protection is disabled but sessions are still enabled (this is a very bad idea), you are not explicitly returning 401 for unauthorized requests (default is 302 redirect to login which is non-sense for a resource server), there is nothing about CORS configuration which is something you'll probably bump into, and to finish, my guess is you still haven't written a serious keykloakJwtAuthConverter.