提前感谢您阅读此问题。
我正在使用:
spring-security-oauth2:2.0.7.RELEASE
spring-cloud-security:1.0.1.RELEASE
spring-session:1.0.1.RELEASE
并且在单点登录中使用spring-security-oauth2
(通过OAuth2ClientContext
)时,会对Redis数据存储中spring-session
@EnableRedisHttpSession
的持久性提出疑问({ {1}}),反向代理(@EnableOAuth2Sso
)网关。
在我看来,@EnableZuulProxy
中创建的SessionScoped
JdkDynamicAopProxied DefaultOAuth2ClientContext
未在Redis数据存储中正确保留。
org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
调试@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);
}
}
没有oauth2ClientContext
的创建表明(按预期)bean会在每个客户端会话中实例化一次并存储在@EnableRedisHttpSession
中。除了在Spring HttpSession
的{{1}}中存储OAuth2 bearerToken
之外,此实例还将重复用于存储提取的OAuth2 accessToken
详细信息。
但是,一旦使用SecurityContext
,org.springframework.security.core.Authentication
bean将首先在会话创建时创建,但也会在稍后创建(同时仍使用相同的客户端会话)。调试Redis客户端会话内容确认会话创建未正确保留@EnableRedisHttpSession
:
在我们检索OAuth2 oauth2ClientContext
之前(NO SpringContext,NO oauth2ClientContext
):
bearerToken
我们检索到OAuth2 scopedTarget.oauth2ClientContext
后(SpringContext保持不变,但是没有~$ 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"
):
bearerToken
如果我们现在尝试访问其中一个配置程序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
实例(因为Redis没有保留,org.springframework.security.oauth2.client.DefaultOAuth2ClientContext#getAccessToken
oauth2ClientContext
。
有趣的是,此实例稍后将保留在Redis中(但null
实例仍然存在,因为AccessToken
未被重新询问):
null
创建一个简单的AccessToken
注入的bean按预期工作,但是在Redis中正确地保存了bean。
~$ 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"
通过添加以下依赖项,可以在@ dave-syer示例中为OAuth2 reverse proxy gateway重现所述问题:
ScopedProxyMode.TARGET_CLASS
以及UiApplication中的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;
}
}
注释。
我们是否应该忽略自动配置中的<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>
并手动创建具有不同设置的@EnableRedisHttpSession
以在Redis中启用org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
持久性?如果是这样,你能举个例子吗?
否则:如何在Redis中保留oauth2ClientContext
?
提前许多人在阅读这个问题时试图提供帮助。
答案 0 :(得分:3)
那里有一个已知问题(https://github.com/spring-projects/spring-session/issues/129和https://github.com/spring-projects/spring-boot/issues/2637)。您可以通过添加RequestContextFilter
来解决此问题。
答案 1 :(得分:3)
@ 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;
}
}
对象持久性的配置。如果这可以帮助某人......
[
答案 2 :(得分:1)
我碰到了这篇文章,我遇到了完全相同的问题,但有一些细微的差异:
但是,这可能会节省一些未来的读者的时间,上述解决方案也对我有用。由于我没有使用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>
如果这可以帮助您节省几个小时来深入研究Stackoverflow和其他网站,那将使我很开心。