Spring Security Oauth2 : Possible CSRF detected
使用Spring Security 作為 Oauth2 授權服務器時,在授權服務器登錄授權后,重定向到客戶端服務器時,出現了401 Unauthorized 錯誤。明明已經授權了,為何還會未授權了。
跟蹤代碼發現,拋出了這個異常:
"Possible CSRF detected - state parameter was required but no state could be found"
導致這個異常的原因是,我在本地部署調試授權服務程序,和客戶端服務程序,均采用的是localhost域名,只是端口不同,這就導致兩個web程序在寫Session的cookie標識id(JSESSIONID)
時會被覆蓋。
具體發送的場景流程是,授權服務登錄授權后,會重定向到客戶端的授權地址,這時會在session域中取出OAuth2ClientContext ,代碼在OAuth2RestOperationsConfiguration類中:
@Bean @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) public DefaultOAuth2ClientContext oauth2ClientContext() { return new DefaultOAuth2ClientContext(this.accessTokenRequest); }
此時,由於Session的標識id被覆蓋,自然認為不在一個會話中,那就不會取到原來在客戶端要求授權跳轉前存放的OAuth2ClientContext,也就導致了爆出這個異常:
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");
}
}
//省略代碼...
return form;
}
那么如何解決這個問題呢?
1.為不同web程序采用不同的域名,由於是本地部署,那么可以為本機配置幾個127.0.0.1 的 域名,如同localhost 指向 本機回還地址一樣。這個本機域名配置可以自行百度。
2.為session的標識id采用不同的名稱,如授權服務器用SESSIONID,客戶端JSESSIONID不變.(如果多個客戶端,那就重命名,以免干涉),以下是如何設置:
2.1 Spring Mvc web.xml
<session-config> <cookie>
<name>SESSIONID</name>
</cookie> </session-config>
2.2 Spring MVC JavaConfig
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.getSessionCookieConfig().setName("SESSIONID");
}
//省略其他配置
}
2.3 SpringBoot
@SpringBootApplication
@ComponentScan(basePackages = "com.test")
public class LoginApplication implements ServletContextInitializer {
public static void main(String[] args) {
SpringApplication.run(LoginApplication.class, args);
}
@Override
public void onStartup(ServletContext servletContext)
throws ServletException {
servletContext.getSessionCookieConfig().setName("SESSIONID");
}
}
或者在application.yml 文件中配置:
server:
port: 8081
tomcat:
uri-encoding: UTF-8
session: cookie: name: SESSIONID
