21

I am trying to get a basic in-memory OAuth2 server running using the Spring Libraries. I have been following the sparklr example.

I currently have configured the Server and almost everything is working, however I cannot access my restricted resource from the resource server.

My test workflow:

  1. Access the oauth authorized URI to start the OAuth2 flow: http://localhost:8080/server/oauth/authorize?response_type=code&client_id=client

  2. Redirect to the login page: http://localhost:8080/server/login

  3. Handle the approval and redirect to my configured redirect page w/ a code parameter: http://localhost:8080/client?code=HMJO4K

  4. Construct a GET request using Basic Auth using the client id and secret along with the grant type and code: http://localhost:8080/server/oauth/token?grant_type=authorization_code&code=HMJO4K

  5. Receive an access_token and refresh token object in return

    { access_token: "f853bcc5-7801-42d3-9cb8-303fc67b0453" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in: 299 scope: "read write" }

  6. Attempt to access a restricted resource using the access_token: http://localhost:8080/server/me?access_token=f853bcc5-7801-42d3-9cb8-303fc67b0453

  7. Receive an invalid token reply

    { error: "invalid_token" error_description: "Invalid access token: f853bcc5-7801-42d3-9cb8-303fc67b0453" }

  8. POST to the token uri again to refresh token: http://localhost:8080/server/oauth/token?grant_type=refresh_token&refresh_token=57100377-dea9-4df0-adab-62e33f2a1b49

  9. Receive a new token

    { access_token: "ed104994-899c-4cd9-8860-43d5689a9420" token_type: "bearer" refresh_token: "57100377-dea9-4df0-adab-62e33f2a1b49" expires_in: 300 scope: "read write" }

I am really not sure what I am doing wrong, but it appears that everything other than accessing the restricted uri is working. Here is my configuration:

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(SERVER_RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and().requestMatchers()
                    .antMatchers("/me")
                .and().authorizeRequests()
                    .antMatchers("/me").access("#oauth2.clientHasRole('ROLE_CLIENT')")
            ;
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthotizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private ClientDetailsService clientDetailsService;

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                .withClient("client")
                    .resourceIds(SERVER_RESOURCE_ID)
                    .secret("secret")
                    .authorizedGrantTypes("authorization_code", "refresh_token")
                    .authorities("ROLE_CLIENT")
                    .scopes("read","write")
                    .redirectUris("http://localhost:8080/client")
                    .accessTokenValiditySeconds(300)
                    .autoApprove(true)
            ;
        }

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

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                .tokenStore(tokenStore())
                .userApprovalHandler(userApprovalHandler())
                .authenticationManager(authenticationManager)
            ;
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.realm("oauth");
        }

        @Bean
        public ApprovalStore approvalStore() throws Exception {
            TokenApprovalStore store = new TokenApprovalStore();
            store.setTokenStore(tokenStore());
            return store;
        }

        @Bean
        public UserApprovalHandler userApprovalHandler() throws Exception {
            TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
            handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
            handler.setClientDetailsService(clientDetailsService);
            handler.setTokenStore(tokenStore());

            return handler;
        }
    }
}

Is there something I am missing or am I approaching this incorrectly? Any help would be greatly appreciated.

jyore
  • 4,715
  • 2
  • 21
  • 26
  • After some more testing, I still cannot get it to work. Somehow, it seems that the ResourceServer is not loading the correct token store or something. I have a token store bean and autowired it into a controller which will print the tokens for my client, which works fine. I autowire the same token store bean (using unique qualifier) into a custom authentication manager, and it cannot find any tokes in the store. I am really not sure how this is possible, unless something is implicitly session scoped?? – jyore Apr 14 '15 at 03:10

3 Answers3

8

Your step #6 is wrong - the access token should not be sent in the URL as it is vulnerable this way. rathen than GET, use POST.

Besides, I don't understand your step #1 - why do you call /oauth/authorize? it should be done implicitly when you try to get a protected resource. I mean, your flow should start with:

Attempt to access a restricted resource using the access_token: http://localhost:8080/server/me

Then the negotiation will start "behind the scenes": a redirect to "/oauth/authorize" etc.

In addition, in step #8, note that you are not asking for "another access token", but instead it is a request for "refresh token". As if your access-token was expired.

Note: The identity provider and the resource server should share the tokenStore! Read here: Spring Security OAuth2 pure resource server

HTH

Community
  • 1
  • 1
OhadR
  • 8,276
  • 3
  • 47
  • 53
  • Thanks for the reply. I have tried GET & POST when trying to access the resource server. I have tried sending as query param, form data, and as the header `Authorization: Bearer ` and in every scenario, I continue to get the 'invalid token' response. I have setup some debug endpoints to dump the current tokens by client and by user and my token is in both lists. – jyore Apr 13 '15 at 12:22
  • the resource server must know how to parse the token... did you write the protected resource? – OhadR Apr 13 '15 at 19:34
  • The resource is simply a spring-mvc endpoint setup to return a json string via `@RequestMapping("/me") @ResponseBody public String me() { return "hello";}`. The `org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter` is rejecting the token. Since posting, I've tried to add to the configuration of `ResourceServerSecurityConfirer` a reference to both the `TokenStore` and the `TokenServices` beans, neither of which have worked. – jyore Apr 13 '15 at 19:46
  • try to stop (breakpoint) in ResourceServerTokenServices.loadAuthentication() - do you get there? – OhadR Apr 14 '15 at 04:59
  • Yes it does. This call returns null `OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);`. Did some more digging and it seems the `TokenStore` is stored correctly by the issuer. However, there are no tokens in the token store when the filter is attempting to authenticate the token. This leads me to believe that resource server side and the authorization server side are not getting the same token store, despite it seeming like they are getting set the same in the config. – jyore Apr 14 '15 at 06:31
  • 2
    indeed. the identity provider and the resource server should share the tokenStore. – OhadR Apr 14 '15 at 08:26
  • I have another Spring OAuth2 problem. Are you willing to take a look? Here is the link: http://stackoverflow.com/questions/36899386/connecting-a-clientdetailsservice-to-a-custom-oauth2requestfactory – CodeMed Apr 27 '16 at 19:56
7

The problem ended up being that the resource server and the authorization server were not getting the same token store reference. Not sure how the wiring was not working correctly, but using a fixed object in the configuration class worked like a charm. Ultimately, I'll move to a persistence backed token store, which probably would not have had any issues.

Thanks goes to @OhadR for the answer and the help!

Ultimately, I simplified the configuration, went thru the same workflow, and it worked out

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    private static InMemoryTokenStore tokenStore = new InMemoryTokenStore();


    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

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

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.requestMatchers().antMatchers("/me").and().authorizeRequests().antMatchers("/me").access("#oauth2.hasScope('read')");
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;


        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore).approvalStoreDisabled();
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                .withClient("client")
                    .authorizedGrantTypes("authorization_code","refresh_token")
                    .authorities("ROLE_CLIENT")
                    .scopes("read")
                    .resourceIds(SERVER_RESOURCE_ID)
                    .secret("secret")
            ;
        }
    }
}

Anyone that stumbles upon this post, I recommend looking more at the unit tests for example rather than the full blown sparklr/tonr example, as it has a lot of extra configuration that are not necessarily needed to get started.

jyore
  • 4,715
  • 2
  • 21
  • 26
  • 1
    thanks for posting this question and answer. It helped with my own research into this. You should mark OhadR's response as a correct answer though because he led you to the right conclusion. – pacman Sep 22 '15 at 15:34
  • I ntice that your answer involves an `AuthorizationServerConfigurerAdapter`. I am getting an `org.springframework.security.oauth2.provider.NoSuchClientException` error in a Spring OAuth2 app that seems to result from my `AuthorizationServerConfigurerAdapter`. Are you willing to take a look? Here is the link: http://stackoverflow.com/questions/36899386/connecting-a-clientdetailsservice-to-a-custom-oauth2requestfactory – CodeMed Apr 27 '16 at 19:55
  • I am online if you are willing to give me a hint on the other question. Not sure where to start with it. – CodeMed Apr 28 '16 at 17:23
0

This works for me:

@Configuration
public class Oauth2ServerConfiguration {

    private static final String SERVER_RESOURCE_ID = "oauth2-server";

    @Autowired
    private TokenStore tokenStore;

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

    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

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

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // ... Not important at this stage
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;


        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore).approvalStoreDisabled();
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //... Not important at this stage
        }
    }
}
fywe
  • 433
  • 1
  • 7
  • 17