Home > Software design >  Spring Oauth2 Authorization Server - How to use multiples JWK Keys
Spring Oauth2 Authorization Server - How to use multiples JWK Keys

Time:12-06

I have a requirement in my system that in some flows I have to use a JWT with a specific private/public key and other flows have to user another JWT with other keys.

I'm using spring oauth2 authorization server 1.0.0.

When I try to set two keys, it works okay to generate the jwks endpoint, but when I do the POST /oauth2/token I got the following exception:

org.springframework.security.oauth2.jwt.JwtEncodingException: An error occurred while attempting to encode the Jwt: Found multiple JWK signing keys for algorithm 'RS256'
    at org.springframework.security.oauth2.jwt.NimbusJwtEncoder.selectJwk(NimbusJwtEncoder.java:128) ~[spring-security-oauth2-jose-6.0.0.jar:6.0.0]
    at org.springframework.security.oauth2.jwt.NimbusJwtEncoder.encode(NimbusJwtEncoder.java:108) ~[spring-security-oauth2-jose-6.0.0.jar:6.0.0]
    at org.springframework.security.oauth2.server.authorization.token.JwtGenerator.generate(JwtGenerator.java:159) ~[spring-security-oauth2-authorization-server-1.0.0.jar:1.0.0]
    at org.springframework.security.oauth2.server.authorization.token.JwtGenerator.generate(JwtGenerator.java:58) ~[spring-security-oauth2-authorization-server-1.0.0.jar:1.0.0]
    at org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator.generate(DelegatingOAuth2TokenGenerator.java:59) ~[spring-security-oauth2-authorization-server-1.0.0.jar:1.0.0]
    at org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider.authenticate(OAuth2ClientCredentialsAuthenticationProvider.java:125) ~[spring-security-oauth2-authorization-server-1.0.0.jar:1.0.0]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-6.0.0.jar:6.0.0]
    at org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter.doFilterInternal(OAuth2TokenEndpointFilter.java:167) ~[spring-security-oauth2-authorization-server-1.0.0.jar:1.0.0]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]

Is that okay my concept of having to JWK keys in the same authorization server?

How can I implement the authorization server to use in a specific client credentials request one JWK key, and in another client credential request another JWK key?

My code:

@EnableWebSecurity
@Configuration
@Slf4j
public class AuthSecurityConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.formLogin(Customizer.withDefaults()).build();
    }

    @Bean
    public SecurityFilterChain authFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        return http.formLogin(Customizer.withDefaults()).build();
    }

    public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder
            ,JdbcTemplate jdbcTemplate
            ) {
        JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

                return clientRepository;
    }

    @Bean
    public OAuth2AuthorizationService auth2AuthorizationService(JdbcOperations jdbcOperations,
                                                                RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(
                jdbcOperations,
                registeredClientRepository
        );
    }

    @Bean
    public OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService(JdbcOperations jdbcOperations,
                                                                               RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(
                jdbcOperations,
                registeredClientRepository
        );
    }

   @Bean
    public JWKSet jwkSet(AuthProperties authProperties) throws Exception {
        List<JWK> keys = new ArrayList<>();
        
        for (JksProperties jwk : authProperties.getJksList()) {
            keys.add(loadRsa(jwk));
        }
        
        return new JWKSet(keys);
    }

   @Bean
    public JWKSource<SecurityContext> jwkSource(JWKSet jwkSet) {
        
        return ((jwkSelector, securityContext) -> jwkSelector.select(jwkSet));
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }


    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

CodePudding user response:

I'm not sure I fully understand the RFC-7517, but it seams that it is allowed to have several keys, with no particular restriction.

I'm surprised because from the decoding side, I'd expect to fetch from the JWKS endpoint THE key to decode JWTs with a given algorithm: it would be rather inneficient to try several keys in turn untill one works...

Have you considered to run several authorization-server instances, each with one specific key? I guess your clients would know which authorization-server to contact, but resource-servers would need multi-tenancy (accept identities issued by several issuers). You'll have to provide JwtIssuerAuthenticationManagerResolver bean for that:

http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));

I have written thin wrappers around spring-boot-starter-oauth2-resource-server which support this kind of scenario with configuration from properties file only (the bean above is provided autmatically):

<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <!-- replace "webmvc" with "weblux" if your app is reactive -->
    <!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
    <artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
    <!-- this version is to be used with spring-boot 3.0.0, use 5.x for spring-boot 2.6.x or before -->
    <version>6.0.7</version>
</dependency>
@Configuration
@EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443
com.c4-soft.springaddons.security.issuers[0].authorities.claims=groups,roles
com.c4-soft.springaddons.security.issuers[1].location=https://localhost:8444
com.c4-soft.springaddons.security.issuers[1].authorities.claims=groups,roles


com.c4-soft.springaddons.security.cors[0].path=/some-api

CodePudding user response:

OAuth has a built-in mechanism to manage multiple keys, called a key identifier (kid). This provides a way for authorization servers to automate token signing key renewal, where both old and new keys are in use, but newer tokens are issued with the newer key.

Check these two things:

  • The JWKS must return multiple public JWK entries with distinct kid values

  • When a JWT is issued to a client, it must include a kid in its JWT header that maps to the public key with which to verify it

Often an authorization server will enable you to have different token issuers configured, and you can then associate a token issuer to clients. This should have no impact on application code, since the JWKS mechanism and kids should take care of it.

  • Related