使用 spring-session 和 spring-cloud-security 时,OAuth2ClientContext (spring-security-oauth2) 不会保留在 Redis
Posted
技术标签:
【中文标题】使用 spring-session 和 spring-cloud-security 时,OAuth2ClientContext (spring-security-oauth2) 不会保留在 Redis 中【英文标题】:OAuth2ClientContext (spring-security-oauth2) not persisted in Redis when using spring-session and spring-cloud-security 【发布时间】:2015-09-07 16:59:50 【问题描述】:非常感谢您阅读此问题。
设置
我正在使用:
spring-security-oauth2:2.0.7.RELEASE
spring-cloud-security:1.0.1.RELEASE
spring-session:1.0.1.RELEASE
当在单点登录 (@EnableOAuth2Sso
)、反向代理 (@ 987654332@) 网关。
问题
在我看来,org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
中创建的 SessionScoped
JdkDynamicAopProxied DefaultOAuth2ClientContext
未正确保存在 Redis 数据存储中。
@Configuration
@ConditionalOnBean(OAuth2SsoConfiguration.class)
@ConditionalOnWebApplication
protected abstract static class SessionScopedConfiguration extends BaseConfiguration
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2ClientContext oauth2ClientContext()
return new DefaultOAuth2ClientContext(accessTokenRequest);
在没有@EnableRedisHttpSession
的情况下调试oauth2ClientContext
的创建表明(如预期的那样)bean 将在每个客户端会话中实例化一次并存储在HttpSession
中。除了将 OAuth2 accessToken
存储在 Spring SecurityContext
的 org.springframework.security.core.Authentication
中之外,该实例随后将被重用以存储获取的 OAuth2 bearerToken
详细信息。
但是,一旦使用@EnableRedisHttpSession
,oauth2ClientContext
bean 将首先在会话创建时创建,但也会在稍后创建(同时仍使用相同的客户端会话)。调试 Redis 客户端会话内容确认 oauth2ClientContext
未通过会话创建正确持久化:
在我们检索 OAuth2 bearerToken
(没有 SpringContext,没有 scopedTarget.oauth2ClientContext
)之前:
~$ redis-cli hkeys "spring:session:sessions:17c5e80b-390c-4fd6-b5f9-a6f225dbe8ea"
1) "maxInactiveInterval"
2) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
3) "lastAccessedTime"
4) "creationTime"
5) "sessionAttr:SPRING_SECURITY_SAVED_REQUEST"
在我们检索到 OAuth2 bearerToken
(SpringContext 持续存在,但没有 scopedTarget.oauth2ClientContext
)之后:
~$ redis-cli hkeys "spring:session:sessions:844ca2c4-ef2f-43eb-b867-ca6b88025c8b"
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) "lastAccessedTime"
3) "creationTime"
4) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
5) "sessionAttr:SPRING_SECURITY_CONTEXT"
6) "maxInactiveInterval"
如果我们现在尝试访问配置器Zuul
的一个路由(因此需要调用org.springframework.security.oauth2.client.DefaultOAuth2ClientContext#getAccessToken
),将创建oauth2ClientContext
的另一个实例(因为没有持久化Redis,带有null
AccessToken
。
有趣的是,这个实例稍后会被持久化在 Redis 中(但是 null
实例会被持久化,因为 AccessToken
不会被重新请求):
~$ redis-cli hkeys "spring:session:sessions:c7120835-6709-4c03-8d2c-98f830ed6104"
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
3) "sessionAttr:scopedTarget.oauth2ClientContext"
4) "sessionAttr:SPRING_SECURITY_CONTEXT"
5) "maxInactiveInterval"
6) "creationTime"
7) "lastAccessedTime"
8) "sessionAttr:org.springframework.web.context.request.ServletRequestAttributes.DESTRUCTION_CALLBACK.scopedTarget.oauth2ClientContext"
创建一个简单的ScopedProxyMode.TARGET_CLASS
注入的 bean 按预期工作,但是 bean 正确地保存在 Redis 中。
public class HelloWorldService implements Serializable
public HelloWorldService()
System.out.println("HelloWorldService created");
private String name = "World";
public String getName()
return name;
public void setName(String name)
this.name=name;
public String getHelloMessage()
return "Hello " + this.name;
@Configuration
public class AppConfig
private SecureRandom random = new SecureRandom();
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloWorldService myHelloService()
HelloWorldService s = new HelloWorldService();
String name = new BigInteger(130, random).toString(32);
System.out.println("name = " + name);
s.setName(name);
System.out.println("Resource HelloWorldService created = " + s);
return s;
示例
通过添加以下依赖项,可以在OAuth2 reverse proxy gateway 的@dave-syer 示例中重现所描述的问题:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
以及UiApplication中的@EnableRedisHttpSession
注解。
问题
我们是否应该忽略 AutoConfiguration 中的 org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
并手动创建具有不同设置的 oauth2ClientContext
以在 Redis 中启用 spring-session
持久性?如果是这样,你能提供一个例子吗?
否则:如何在 Redis 中持久化oauth2ClientContext
?
提前给任何阅读此问题并试图提供帮助的人。
【问题讨论】:
Jeremie 您是否配置了 oauth2 + spring session 来存储多个应用程序之间的会话?可以分享一些细节吗? @bilak Oauth2 不幸的是不多(只是一个 PoC,使用 JWOT)。但是 Spring-Session 更常见的是,是的。你会对什么样的细节感兴趣? (我不能分享整个代码库,因为应用程序是专有的) 我正在尝试使用网关创建示例应用程序作为所有应用程序look here 的入口点。我不关注的是如何在应用程序之间共享 csrf/sessions 数据。我不想使用 jwt,所以我在考虑 jdbc oauth2 + spring session。但我目前卡在身份验证这一点上......当我登录到 UAA 时,csrf 出现问题......它没有共享,我目前不知道该怎么做。 【参考方案1】:我遇到了这篇文章,我遇到了完全相同的问题,但存在一些细微差别:
我的应用程序不是 Spring Boot 应用程序 我使用 JDBC 持久性而不是 Redis但是,这可能会为未来的读者节省一些时间,上述解决方案也对我有用。由于我没有使用 Spring Boot,因此我将在此处发布解决方案,以便使用 web.xml 配置在非 Spring Boot 应用程序中应用。
“诀窍”是在 web.xml 中定义 RequestContextFilter。就我的测试而言,我没有看到将请求上下文过滤器放在请求上下文侦听器旁边的任何边界效应。
重要的是过滤器的顺序。您需要在 web.xml 中按此顺序定义过滤器:
会话存储库过滤器 请求上下文过滤器 安全过滤器比如:
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果这可以帮助您节省几个小时深入研究 *** 和其他网站的时间,我会很开心。
【讨论】:
【参考方案2】:@dave-syer 的提示是正确的。
我在这里发布可用于设置RequestContextFilter
和启用spring-session
持久性spring-security-oauth
对象的配置。如果这可以帮助某人......
@Configuration
public class RequestContextFilterConfiguration
@Bean
@ConditionalOnMissingBean(RequestContextFilter.class)
public RequestContextFilter requestContextFilter()
return new RequestContextFilter();
@Bean
public FilterRegistrationBean requestContextFilterChainRegistration(
@Qualifier("requestContextFilter") Filter securityFilter)
FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
registration.setOrder(SessionRepositoryFilter.DEFAULT_ORDER + 1);
registration.setName("requestContextFilter");
return registration;
【讨论】:
【参考方案3】:那里存在一个已知问题(https://github.com/spring-projects/spring-session/issues/129 和 https://github.com/spring-projects/spring-boot/issues/2637)。您可以通过添加 RequestContextFilter
来解决此问题。
【讨论】:
非常感谢@dave-syer 花时间分析这个问题。我将看看使用RequestContextFilter
的解决方案并报告...以上是关于使用 spring-session 和 spring-cloud-security 时,OAuth2ClientContext (spring-security-oauth2) 不会保留在 Redis的主要内容,如果未能解决你的问题,请参考以下文章
使用 Spring-Session 时更新 Session 中的 Principal
如何使用 JDBC 在 spring-session 中初始化模式
在使用 spring-session 的单元测试中,身份验证不应为空
如何在一个应用程序中配置 spring-session 以支持 HeaderHttpSessionStrategy 和 CookieHttpSessionStrategy?