为什么 AccessTokenRequest 的 PreservedState 永远为空,并产生与 CSRF 相关的 InvalidRequestException? [英] Why is AccessTokenRequest's PreservedState perpetually null with a resultant CSRF related InvalidRequestException?

查看:52
本文介绍了为什么 AccessTokenRequest 的 PreservedState 永远为空,并产生与 CSRF 相关的 InvalidRequestException?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为上下文,我一直在尝试获得一个相当简单的 @SprintBootApplication,其中包含一个额外的 @EnableOAuth2Sso 注释,并与 WSO2 身份服务器集成了一段时间.

As context, I've been trying to get a fairly simple @SprintBootApplication with an additional @EnableOAuth2Sso annotation integrated with WSO2 Identity Server for quite some time now.

在我看来,让这个工作应该是一个配置问题(正如在 Spring Cloud Security) - 但到目前为止我没有运气.

In my mind getting this working should be a matter of configuration (as advertised on Spring Cloud Security) - but I've had no luck thus far.

为了了解发生了什么,我使用调试器逐步执行 spring-security-oauth2 代码以找出发生了什么.这样做时,我注意到我的 AccessTokenRequest 的 PreservedState 永远为空,并产生与 CSRF 相关的 InvalidRequestException.这是相关代码:

In an effort to understand what is going on I've used my debugger to step through spring-security-oauth2 code to figure out what is going on. In doing so I've noticed that my AccessTokenRequest's PreservedState is perpetually null with a resultant CSRF related InvalidRequestException. This is the relevant code:

public class AuthorizationCodeAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {

....

private MultiValueMap<String, String> getParametersForTokenRequest(AuthorizationCodeResourceDetails resource,
        AccessTokenRequest request) {

    MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
    form.set("grant_type", "authorization_code");
    form.set("code", request.getAuthorizationCode());

    Object preservedState = request.getPreservedState();
    if (request.getStateKey() != null || stateMandatory) {
        // The token endpoint has no use for the state so we don't send it back, but we are using it
        // for CSRF detection client side...
        if (preservedState == null) {
            throw new InvalidRequestException(
                    "Possible CSRF detected - state parameter was required but no state could be found");
        }
    }

当涉及到上面的代码时,我在使用 admin/admin 登录并且我的 Web 应用程序收到了授权代码后,我已经批准了声明:

When it comes to the above bit of code, I'm at the point where I've approved the claim after having logged in with admin/admin and my web application has received the auth code:

http://localhost:9998/loginstate=Uu8ril&code=20ffbb6e4107ce3c5cf9ee22065f4f2 

鉴于我首先需要做的就是让登录部分工作,我尝试禁用 CSRF,但无济于事.

Given that all I need to do in the first instance is to get the login part working I've tried disabling CSRF, to no avail.

相关配置如下:

spring:
  profiles: default
security:
  oauth2:
    client:
      accessTokenUri: https://localhost:9443/oauth2/token
      userAuthorizationUri: https://localhost:9443/oauth2/authorize
      clientId: yKSD9XwET9XJ3srGEFXP6AfHhAka
      clientSecret: zuPTcdJH435h3wgl055XNZ5ffNMa
      scope: openid
      clientAuthenticationScheme: header
     resource:
      userInfoUri: https://localhost:9443/oauth2/userinfo?schema=openid

就我自己的调查工作而言,令人担忧的是 DefaultOAuthClientContext preserved 状态在需要使用之前被清除,这似乎是一个顺序问题.

In terms of my own investigative efforts, of concern is that in the DefaultOAuthClientContext preserved state gets cleared right before it needs to used, this appears to be a sequencing issue.

我使用的是最新版本的 Spring Boot (1.3.0) 和 WSO2 Identity Server (5.0).同样使用 spring-security-oauth 2.0.8.

I'm using the latest releases of Spring Boot (1.3.0) and WSO2 Identity Server (5.0). Also using spring-security-oauth 2.0.8.

推荐答案

事实证明,提供的代码部分中引用的preservedState为null的原因是因为正在创建bean Oauth2ClientContext的新实例,这正是不应该发生的事情 - OAuth2ClientContext 的全部目的是存储状态.就 OAuth2 协议(RFC 6749)而言,保留状态对于跨站点请求伪造预防(请参阅第 10.12 节).

As it turns out, the reason why the cited preservedState is null in the section of code provided is because a new instance of bean Oauth2ClientContext is being created which is precisely what should not be happening - the entire purpose of the OAuth2ClientContext is to store state. In terms of the OAuth2 protocol (RFC 6749), preserving the state is important in terms of Cross-Site Request Forgery prevention (see Section 10.12).

选择它是启用调试日志记录并将针对 WSO2 IS 生成的输出与通过工作示例看到的输出进行比较的简单问题.就我而言,我总是返回的工作示例是 Spring 团队自己提供的示例.

Picking this up is a simple matter of enabling debug logging and also comparing the output generated against WSO2 IS with what one sees with a working example. In my case the working example that I always revert back to is the one provided by the Spring team themselves.

这是客户端配置(application.yml),然后使用Spring团队SSO服务器进行日志输出测试:

This is the client configuration (application.yml) and then log output testing using the Spring team SSO server:

spring:
  profiles: default
security:
  oauth2:
    client:
      accessTokenUri: http://192.168.0.113:32768/uaa/oauth/token
      userAuthorizationUri: http://192.168.0.113:32768/uaa/oauth/authorize
  clientId: acme
  clientSecret: acmesecret
resource:
  jwt:
    keyValue: |
      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzEtgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+TNu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB
      -----END PUBLIC KEY-----
  id: openid
  serviceId: ${PREFIX:}resource

注意没有提到OAuth2ClientContext的创建.

Take note that there is no line mentioned the creation of OAuth2ClientContext.

DEBUG o.s.security.web.FilterChainProxy - /login?code=9HLSpP&state=G9kpy3 at position 6 of 12 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login'
DEBUG o.s.s.o.c.f.OAuth2ClientAuthenticationProcessingFilter - Request is to process authentication
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Getting access token request
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.accessTokenRequest'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.accessTokenRequest'
INFO  o.s.s.o.client.OAuth2RestTemplate - Longer lived state key: G9kpy3
INFO  o.s.s.o.client.OAuth2RestTemplate - Removing preserved state in oauth2context
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Found preserved state: http://localhost:9999/login

这是客户端配置 (application.yml),然后使用 WSO2IS 5.0.0 进行日志输出测试:

This is the client configuration (application.yml) and then log output testing using WSO2IS 5.0.0:

spring:
  profiles: wso2
server:
  port: 9998
security:
  oauth2:
    client:
      accessTokenUri: https://localhost:9443/oauth2/token
      userAuthorizationUri: https://localhost:9443/oauth2/authorize
      clientId: yKSD9XwET9XJ3srGEFXP6AfHhAka
      clientSecret: zuPTcdJH435h3wgl055XNZ5ffNMa
      scope: openid
      clientAuthenticationScheme: header
    resource:
      userInfoUri: https://localhost:9443/oauth2/userinfo?schema=openid

注意Creating instance of bean 'scopedTarget.oauth2ClientContext'.

Take note of the line with Creating instance of bean 'scopedTarget.oauth2ClientContext'.

DEBUG o.s.security.web.FilterChainProxy - /login?state=PWhQwv&code=372ff0c197a4c85a0caf070cc9a6678 at position 6 of 12 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login'
DEBUG o.s.s.o.c.f.OAuth2ClientAuthenticationProcessingFilter - Request is to process authentication
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.oauth2ClientContext'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration$SessionScopedConfiguration$ClientContextConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.oauth2ClientContext'
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Getting access token request
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.accessTokenRequest'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.accessTokenRequest'
INFO  o.s.s.o.client.OAuth2RestTemplate - Longer lived state key: PWhQwv
INFO  o.s.s.o.client.OAuth2RestTemplate - Removing preserved state in oauth2context
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Found preserved state: null

最后,下一个调用端口自然是确定为什么 OAuth2ClientContext 不是使用 WSO2 IS 配置创建的.调查表明,这是因为 WSO2 IS 没有传回预期的 JSESSIONID,因此找不到会话范围的 OAuth2ClientContext.

Finally, the next port of call is naturally to ascertain why the OAuth2ClientContext was not created with the WSO2 IS config. Investigation has shown that it is because WSO2 IS is not passing back an expected JSESSIONID, hence the session scoped OAuth2ClientContext will not be found.

如果不顾一切地解决这种情况的潜在黑客是克隆 Spring OAuth 2 并执行以下操作:

A potential hack to fix this situation if desperate is to clone Spring OAuth 2 and do the following:

在 AuthorizationCodeAccessTokenProvider 类中执行以下操作,修改请求中的保留状态.

In class AuthorizationCodeAccessTokenProvider do the following with the hack being to change the preserved state in the request.

    private MultiValueMap<String, String>     getParametersForTokenRequest(AuthorizationCodeResourceDetails resource,
        AccessTokenRequest request) {

            MultiValueMap<String, String> form = new   LinkedMultiValueMap<String, String>();
            form.set("grant_type", "authorization_code");
            form.set("code", request.getAuthorizationCode());

            request.setPreservedState("http://localhost:9998/login");
            Object preservedState = request.getPreservedState();

这篇关于为什么 AccessTokenRequest 的 PreservedState 永远为空,并产生与 CSRF 相关的 InvalidRequestException?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆