Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot - Using JWT, OAuth, and Separate Resource and Auth Servers

I am attempting to build a Spring application that uses JWT tokens and the OAuth2 protocol. I have the Authentication Server running thanks to this tutorial. However, I am struggling with getting the Resource Server to function properly. From following the article, and thanks to a response to a prior question, this is my current attempt:

Security config for Resource Server:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer clientID;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(signingKey);
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean ResourceServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        OAuth2AuthenticationManager authManager = new OAuth2AuthenticationManager();
        authManager.setTokenServices(tokenService());
        return authManager;
    }

}

Resource Server config:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private ResourceServerTokenServices tokenServices;

@Value("${security.jwt.resource-ids}")
private String resourceIds;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId(resourceIds).tokenServices(tokenServices);
}

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

}

Security config for Authorization Server (from noted tutorial):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer encodingStrength;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new ShaPasswordEncoder(encodingStrength));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .realmName(securityRealm)
                .and()
                .csrf()
                .disable();

    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

Now, when I attempt to make a request to the Resource server, I receive an error such as follows:

{"error":"invalid_token","error_description":"Invalid access token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiam9obi5kb2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXh wIjoxNTE1MTE3NTU4LCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF"}

I have a couple questions:

  • To my understanding, the issue is often associated with token stores. How does one handle separting the servers when a JwtTokenStore is used?
  • Second, currently, my Resource app relies on having access to a key. To my understanding of the JWT and 0Auth specs, this should not be necessary. Rather, I should be able to delegate the validation to the Authentication server itself. From the Spring docs, I thought the following property might be applicable security.oauth2.resource.token-info-uri=http://localhost:8080/oauth/check_token. However, if I attempt to not rely on a key with my Resource Server, then I run into difficulties setting my ResourceServerTokenService. The service expects a token store, the JWTTokenStore uses a JwtAccessTokenConverter, and the converter uses a key (removing the key resulted in the same invalid token error I experienced earlier).

I am really struggling to find articles that show how to separate the Auth and Resource server. Any advice would be appreciated.

EDIT: Actually, the code for the Resource Server is now failing to compile with the following message:

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key

like image 768
KellyM Avatar asked Jan 04 '18 15:01

KellyM


1 Answers

I tried spring oauth and I came across the same error :

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key

My mistake was that my public certificate was :

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
tadadada
-----END CERTIFICATE-----

And this is not allowed. REMOVE the certificate, just let the public key in this file :

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----

And the startup error will disapear.

For your second question, that's what I understand :

  • The authentication server give you an encrypted token (encrypted with the private key), that contains ALL the permissions of your user.

  • The resource server decrypts the token with the public key, and assumes that the permissions contained in the token are TRUE.

Hopes this help.

like image 129
Oreste Viron Avatar answered Nov 14 '22 21:11

Oreste Viron