使用 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

非常感谢您阅读此问题。

设置

我正在使用:

并且在单点登录中使用 spring-session(通过 @EnableRedisHttpSession)时,会对 spring-security-oauth2 OAuth2ClientContext 在 Redis 数据存储中的持久性有疑问(@EnableOAuth2Sso),反向代理 (@EnableZuulProxy) 网关。

问题

在我看来,在 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 SecurityContextorg.springframework.security.core.Authentication.[=60 中之外,此实例将被重新用于存储获取的 OAuth2 bearerToken 详细信息=]

但是,一旦使用 @EnableRedisHttpSessionoauth2ClientContext bean 将首先在会话创建时创建,但稍后也会创建(同时仍使用相同的客户端会话)。调试 Redis 客户端会话内容确认 oauth2ClientContext 未通过会话创建正确保留:

在我们检索 OAuth2 之前 bearerToken(没有 Spring上下文,没有 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 之后(Spring上下文仍然存在,但没有 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 的另一个实例将是created(因为在 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;
    }
}

例子

通过添加以下依赖项,可以在@dave-syer 示例中针对 OAuth2 reverse proxy gateway 重现所描述的问题:

<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

许多人提前阅读这个问题并试图提供帮助。

这里有一个已知问题 (https://github.com/spring-projects/spring-session/issues/129 and https://github.com/spring-projects/spring-boot/issues/2637)。您可以通过添加 RequestContextFilter.

来解决它

@dave-syer 提示正确。

我在 post 此处配置可用于设置 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;
    }
}

我遇到了这个 post 我遇到了完全相同的问题,但有一些细微差别:

  • 我的应用程序不是 Spring 启动应用程序
  • 我使用JDBC持久化而不是Redis

但是,这可能会为未来的读者节省一些时间,上述解决方案对我也很有效。由于我没有使用 Spring Boot,因此我将在此处发布解决方案,以便在使用 web.xml 配置的非 Spring Boot 应用程序中应用。

"trick"是在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>

如果这能帮助您节省几个小时浏览 Whosebug 和其他网站的时间,那我就开心了。