7

I've been working the last couple days to get an implementation of spring boot / spring security / and java configuration working with spring-security-oauth2. I've managed to work through most of the difficulties, but am stumped as to what is going wrong now.

I am completing the following steps successfully:

  • sending user to the provider to authorize the application to act on their behalf
  • user is prompted to sign in to provider per security
  • user authorizes the app, and the redirect url sends them back to the client app at the original url along with ?code=asdfa&state=asdfasfin the query string

At this point, I believe whatever is using the AuthorizationCodeResourceDetails should be exchanging the authorization code and client app credentials for an access token. This is where the process is failing with the following stack trace.

 Caused by: org.springframework.security.oauth2.common.exceptions.InvalidRequestException: Possible CSRF detected - state parameter was present but no state could be found
    at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.getParametersForTokenRequest(AuthorizationCodeAccessTokenProvider.java:246)
    at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:198)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:564)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:529)
    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:261)
    at com.pvr.apps.admin.user.UserServiceImpl.getAllUsers(UserServiceImpl.java:51)
    at com.pvr.apps.admin.web.IndexController.serveUserList(IndexController.java:35)

Things on the client look like (I also have an @EnableOAuth2Client annotation on the main config).

@Component
public class UserServiceImpl implements UserService {

    @Resource
    @Qualifier("accessTokenRequest")
    private AccessTokenRequest accessTokenRequest;

    public OAuth2ProtectedResourceDetails createResource() {
        AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
        resourceDetails.setScope(Lists.newArrayList("read", "write"));
        resourceDetails.setClientId("admin");
        resourceDetails.setClientSecret("password");
        resourceDetails.setAuthenticationScheme(AuthenticationScheme.query);
        resourceDetails.setAccessTokenUri("http://provider.com:8080/oauth/token");
        resourceDetails.setUserAuthorizationUri("http://provider.com:8080/oauth/authorize");
        return resourceDetails;
    }

    @Override
    public List<User> getAllUsers() {

        RestTemplate template = new OAuth2RestTemplate(createResource(), new DefaultOAuth2ClientContext(accessTokenRequest));

        ResponseEntity<User[]> responseEntity = template.getForEntity("http://provider.com:8080/users/", User[].class);
        return Lists.newArrayList(responseEntity.getBody());
    }
}

And on the provider side of things:

The authorization server config:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{

    @Autowired
    private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AuthenticationManager authenticationManager;


    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .authenticationEntryPoint(authenticationEntryPoint)
                .tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
                .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        endpoints
                .authenticationManager(authenticationManager)
                .accessTokenConverter(converter)
                .tokenStore(new JwtTokenStore(converter));
    }


    // TODO: this should read from a db
    public void configure(ClientDetailsServiceConfigurer clientConfigurer) throws Exception {
        clientConfigurer.inMemory()
                .withClient("admin").secret("password")
                .authorizedGrantTypes(
                        GrantType.PASSWORD.type,
                        GrantType.AUTHORIZATION_CODE.type,
                        GrantType.IMPLICIT.type,
                        GrantType.REFRESH_TOKEN.type
                )
                .authorities("ROLE_TRUSTED_CLIENT")
                .scopes("read", "write", "trust")
                .accessTokenValiditySeconds(60);
    }
}

and the resource server config:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailService;

    @Autowired
    AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
                .formLogin()
                    .loginProcessingUrl("/login.do")
                    .usernameParameter("uid")
                    .passwordParameter("pwd")
                    .loginPage("/login")
                    .failureUrl("/login?error=true")
            .and()
                .userDetailsService(userDetailService);
    }

}
RutledgePaulV
  • 2,568
  • 3
  • 24
  • 47
  • Also, I'm planning on doing a write-up around all of this once I understand it. Current documentation and examples are sparse and a bit jumbled between older workarounds and newer conveniences. @Rob Winch? – RutledgePaulV Dec 23 '14 at 23:29
  • I am also having the same error but I am implementing OAuth2 using three apps Authorize, resource and client server all are running on different ports it is ending up on the same URL as yours. – Suleman khan Aug 22 '15 at 20:00
  • Hi @Paul_R can you share the code for the auth you have written ? Your code structure is very different from other example i have stumbled upon, it will be very helpful. – balboa_21 Dec 19 '16 at 19:20

1 Answers1

6

The state it is looking for would be in the OAuth2ClientContext but since you have just created a new one it is out of scope when it is needed. If you inject the one that comes from @EnableOAuth2Client instead it will be in @Scope("session") so it will be able to resolve the state for you. All the samples in GitHub work that way. Or you can manage the persistence yourself, I guess.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • Thanks Dave, that makes sense. Unfortunately, the server is now providing `OAuth2AccessDeniedException: Invalid token for client`. when trying to fetch the resource. I've confirmed that the access token IS in the header sent. Can you see anything else that looks off or provide some direction? – RutledgePaulV Dec 24 '14 at 16:33
  • Removed the jwt token configuration and now getting a `UserDeniedAuthorizationException`.. does that mean that the provider is not remembering the authorization? – RutledgePaulV Dec 24 '14 at 17:00
  • Your "resource server" isn't actually an OAuth2 resource server (no `@EnableResourceServer`). Maybe you pasted the wrong code? Is `UserDeniedAuthorizationException` on the auth server (seems like an obvious hint). – Dave Syer Dec 24 '14 at 17:20
  • Ah, we figured it out. We had to explicitly set .approvalStoreDisabled() on the EndpointsConfigurer since we weren't approving by scope. The logic in AuthorizationServerEndpointsConfigurer#userApprovalHandler is a bit unclear as we were expecting a TokenStoreUserApprovalHandler to be created to wrap our token store, but in reality it was creating a ApprovalStoreUserApprovalHandler. – RutledgePaulV Dec 24 '14 at 18:00
  • @Dave Syer - I am also having this same problem but configurations files are slight different as I am having 3 apps for handling this stuff. here is my question I asked [here is the link for question](http://stackoverflow.com/questions/32160210/not-able-to-access-oauth-access-token) – Suleman khan Aug 23 '15 at 22:43