作为上下文,相当长时间以来,我一直在尝试获得一个非常简单的@SprintBootApplication以及与WSO2 Identity Server集成的其他@ EnableOAuth2Sso批注。
在我看来,这项工作应该是配置问题(如Spring Cloud Security上所宣传)-但到目前为止,我还没有运气。
为了了解发生了什么,我使用了调试器来逐步检查spring-security- oauth2代码以弄清楚发生了什么。这样做时,我注意到我的AccessTokenRequest的PreservedState永久为null,并产生了与CSRF相关的InvalidRequestException。这是相关代码:
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应用程序已收到身份验证代码后,我已经批准了索赔。
http://localhost:9998/loginstate=Uu8ril&code=20ffbb6e4107ce3c5cf9ee22065f4f2
鉴于我首先要做的就是让登录部分正常工作,所以我尝试禁用CSRF,但无济于事。
相关配置如下:
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中, 保留 状态在需要使用之前就被清除了,这似乎是一个排序问题。
我正在使用最新版本的Spring Boot(1.3.0)和WSO2 Identity Server(5.0)。还使用spring-security- oauth 2.0.8。
事实证明,在所提供的代码部分中所引用的keepedState为null的原因是因为正在创建一个新的Oauth2ClientContext实例,而这恰恰是不应该发生的事- OAuth2ClientContext的整个目的就是存储状态。就OAuth2协议(RFC 6749)而言,保持状态对于跨站点请求伪造的预防非常重要(请参阅第10.12节)。
进行调试非常简单,只需启用调试日志记录,并将WSO2 IS生成的输出与一个工作示例中看到的进行比较。就我而言,我经常回到的工作示例是Spring团队自己提供的示例。
这是客户端配置(application.yml),然后使用Spring小组SSO服务器进行日志输出测试:
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的行 。
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进行日志输出测试:
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
在 创建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
最后,调用的下一个端口自然是要确定为什么未使用WSO2 IS配置创建OAuth2ClientContext。调查表明,这是因为WSO2 IS没有传回预期的JSESSIONID,因此找不到以会话为范围的OAuth2ClientContext。
如果迫切需要解决此问题的潜在方法是克隆Spring OAuth 2并执行以下操作:
在AuthorizationCodeAccessTokenProvider类中,对hack进行以下操作以更改请求中的保留状态。
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();