8

I've a rich web (react based) front end application that sends request to a backend ResourceServer application. The requests are sent with JWT in the header for authentication. My setup does authentication against an Okta Authorization Server and retrieves Groups/Authorities from a separate service.

I have the backend server setup as a Springboot app with Spring Security Oauth2 resource server @EnableResourceServer

@Configuration
@EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception{
        http.requestMatchers().antMatchers("/api/**")
                .and()
                .authorizeRequests().anyRequest().authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenServices(tokenServices()).resourceId("my-okta-resource-server-client-id");
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() throws Exception {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        return tokenServices;
    }

    @Bean
    public TokenStore tokenStore() throws Exception {
       return new JwkTokenStore("https://my-org.oktapreview.com/oauth2/my-auth-server/v1/keys");
    }

With this setup I'm able to validate the JWT token alright using the JwkTokenStore implementation (It internally uses JwkVerifyingJwtAccessTokenConverter to verify the Json Web Keyset from the keys uri supplied). However I'm unable to configure it to either

  1. Pull out specific JWT claims for it to create the Authentication. It only looks at the user_name claim, I'd like to use email or other claims on there.
  2. Inject a custom User Details service. The DefaultUserAuthenticationConverter which encapsulates a UserDetailsService is embedded deep within. This I need to lookup authorities the user is setup with in a separate rest service.

Spring-security-oauth2's JWK related implementation seems closed with most of the JWKs verification related classes not being public. What's a good way to utilize the token verification provided by JwkTokenStore and also be able to have your own user details in there to load authorities.

Software versions in use: Springboot 1.5.2.RELEASE, spring-security-core:4.2.3, spring-security-oauth2:2.0.14,

Amit Kapoor
  • 201
  • 3
  • 7
  • 1
    FYI: Spring's support for OAuth and OpenID Connect was spread across multiple projects and now is being rewritten and consolidated into the newest version of Spring Security proper. The most recent release is 5.0.0.M2 See details here: https://github.com/spring-projects/spring-security/issues/3907 – Kyle Anderson Jul 06 '17 at 22:45
  • I'm already using the updated software versions, Spring security 4.2 which has the support for open-id-connect and oauth2 implementations. – Amit Kapoor Jul 07 '17 at 00:00
  • Spring Security 4.2 only contained support for OpenID 2.0 which is the previous version of the standard. OpenID Connect is the new version and hasn't been supported by Spring until Spring Security 5.0 (which is still being worked on). And the OAuth story was spread across Spring Boot, Spring Security, Spring Security OAuth, Spring Social, etc. Now all of that is being rewritten and conslidated into Spring Security 5.0. So if you need OpenID Connect you must use Spring Security 5.0. If you only require OAuth, then you can leverage the projects already mentioned depending on your needs. – Kyle Anderson Jul 07 '17 at 14:38
  • I have the same challenge. Amit, have you found a solution? Have you switched to Spring Security 5? – Bernd Jun 22 '18 at 09:20
  • Yes Bernd, I'm now on Springboot2/Spring5 and also able to extract custom claims as well as enhance the security context to add roles via a separate service call. – Amit Kapoor Jul 19 '18 at 05:45

1 Answers1

4

You should use the so called DefaultAccessTokenConverter to extract these extra claims and add it to an OAuth2Authentication object.

You can @Autowire the CustomAccessTokenConverter into your ResourceServerConfiguration class and then set it to your JwtTokenStore() configuration.

ResourceServerConfiguration:

@Autowired
private CustomAccessTokenConverter yourCustomAccessTokenConverter;

@Override
public void configure(final ResourceServerSecurityConfigurer config) {
    config.tokenServices(tokenServices()).resourceId(resourceId);
}

@Bean
@Primary
public DefaultTokenServices tokenServices() {
    final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(jwkTokenStore());
    return defaultTokenServices;
}

@Bean
public TokenStore jwtTokenStore() throws Exception {
   return new JwkTokenStore("https://my-org.oktapreview.com/oauth2/my-auth-server/v1/keys", accessTokenConverter());
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setAccessTokenConverter(yourCustomAccessTokenConverter);
    return converter;
}

The CustomAccessTokenConverter can be configured, so that the custom claims get extracted here.

CustomAccessTokenConverter:

@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication = super.extractAuthentication(claims);
        authentication.setDetails(claims);
        return authentication;
    }
}

Afterwards you should be able to access the claims via the OAuth2Authentication object.

git-flo
  • 924
  • 9
  • 22
  • 1
    The problem is that JwtTokenStore (from your answer) isn't using jwk to verify tokens (you have to implement that yourself). JwkTokenStore (mentioned by the OP) does, but doesn't allow any customization (not possible to use a custom JwtAccessTokenConverter). So, you have to choose between reinventing the wheel, or no custom autorities. Seem a lot of resources on the web just choose to use JwtTokenStore, and use static keys instead of jwk (which in turn implies either no key rotation and bad security practices, or downtimes and operational overhead). – ymajoros Jan 09 '20 at 16:12