Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring-Oauth2 Access Token request never succeeds due to missing CSRF 'preserved state'

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);
    }

}
like image 538
RutledgePaulV Avatar asked Dec 23 '14 23:12

RutledgePaulV


People also ask

Is OAuth2RestTemplate deprecated?

Deprecated. See the OAuth 2.0 Migration Guide for Spring Security 5. Rest template that is able to make OAuth2-authenticated REST requests with the credentials of the provided resource.

What is ClientRegistrationRepository?

The ClientRegistrationRepository serves as a repository for OAuth 2.0 / OpenID Connect 1.0 ClientRegistration (s). Note. Client registration information is ultimately stored and owned by the associated Authorization Server.

What is OAuth2RestTemplate?

The main goal of the OAuth2RestTemplate is to reduce the code needed to make OAuth2-based API calls. It basically meets two needs for our application: Handles the OAuth2 authentication flow. Extends Spring RestTemplate for making API calls.


1 Answers

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.

like image 158
Dave Syer Avatar answered Oct 30 '22 04:10

Dave Syer