java 由于缺少 CSRF“保留状态”,Spring-Oauth2 访问令牌请求永远不会成功

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27629373/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:08:58  来源:igfitidea点击:

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

javaspring-securityspring-security-oauth2

提问by RutledgePaulV

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.

过去几天我一直在努力实现 spring boot / spring security / 和使用 spring-security-oauth2 的 java 配置。我已经设法克服了大部分困难,但我对现在出了什么问题感到困惑。

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
  • 将用户发送到提供者以授权应用程序代表他们行事
  • 系统会提示用户按安全性登录提供程序
  • 用户授权应用程序,重定向 url 将它们发送回原始 url 和?code=asdfa&state=asdfasf查询字符串中的客户端应用程序

At this point, I believe whatever is using the AuthorizationCodeResourceDetailsshould 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.

在这一点上,我相信无论使用什么,都AuthorizationCodeResourceDetails应该将授权代码和客户端应用程序凭据交换为访问令牌。这是进程失败并显示以下堆栈跟踪的地方。

 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 @EnableOAuth2Clientannotation on the main config).

客户端上的东西看起来像(我@EnableOAuth2Client在主配置上也有一个注释)。

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

}

采纳答案by Dave Syer

The state it is looking for would be in the OAuth2ClientContextbut 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 @EnableOAuth2Clientinstead 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.

它正在寻找的状态将在,OAuth2ClientContext但是由于您刚刚创建了一个新状态,因此在需要时超出了范围。如果您注入来自的那个@EnableOAuth2Client,它将处于,@Scope("session")因此它将能够为您解决状态。GitHub 中的所有示例都以这种方式工作。或者你可以自己管理持久性,我猜。