Java 如何使Spring Security OAuth2与负载平衡器一起工作?

Java 如何使Spring Security OAuth2与负载平衡器一起工作?,java,spring,spring-security,oauth-2.0,Java,Spring,Spring Security,Oauth 2.0,我们目前有4个Spring应用程序,它们使用SpringSecurityOAuth2项目进行身份验证。这些应用程序是RESTAPI,由我所在公司的其他内部应用程序使用 在开发和QA环境中一切正常,因为我们没有进行负载平衡,现在我们处于预生产阶段,负载平衡器(LB)面临一个问题 这是此问题的工作流程: 客户端发送对oauth令牌的请求 LB将请求重定向到框1 框1验证并返回有效的承载令牌 客户端接收令牌并将其存储起来,以便通过sesion使用 客户端在RESTAPI中发送服务请求,将先前检索到的令

我们目前有4个Spring应用程序,它们使用SpringSecurityOAuth2项目进行身份验证。这些应用程序是RESTAPI,由我所在公司的其他内部应用程序使用

在开发和QA环境中一切正常,因为我们没有进行负载平衡,现在我们处于预生产阶段,负载平衡器(LB)面临一个问题

这是此问题的工作流程:

  • 客户端发送对oauth令牌的请求
  • LB将请求重定向到框1
  • 框1验证并返回有效的承载令牌
  • 客户端接收令牌并将其存储起来,以便通过sesion使用
  • 客户端在RESTAPI中发送服务请求,将先前检索到的令牌添加到头中
  • LB将请求重定向到框2
  • 框2无法进行身份验证,因为它无法识别令牌并返回无效凭据响应
  • 我们正在使用内存中的用户存储:

    <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />
    
    
    

    有没有办法让不同的盒子共享同一个令牌存储?我知道有一个JdbcTokenStore可用于将令牌持久化到db,但我更愿意避免持久化令牌,因为这些应用程序指向只存储业务信息的传统数据库。

    为了验证令牌,所有身份验证服务器和所有资源服务器必须共享相同的
    tokenStore


    这意味着切换到
    JdbcTokenStore
    或自定义
    TokenStore
    实现,该实现能够以某种方式在服务器之间共享令牌(共享数据存储、NFS共享文件系统等)。当然,如果您愿意,甚至可以使用Terracotta或类似的内存共享产品在MemoryTokenStore中共享

    我回答这个问题有点晚,但这可能有助于人们寻找类似的答案。在负载平衡器上使用多个oauth服务器时,需要注意两个主要事项:

    正如@chris-h在他的回答中提到的,您需要确保支持任何oauth服务器发布的访问令牌的信息可以被任何其他oauth服务器读取(并信任)。您可以按照他的建议使用JDBC令牌存储,但这有一个缺点,即如果服务器a必须验证服务器B发布的访问令牌,那么它总是必须访问数据库才能进行验证

    更好的解决方案(IMO)是使用JWT访问令牌,其中验证令牌所需的所有信息都在其中加密。只要所有oauth服务器使用相同的加密密钥,它们就可以读取彼此访问令牌中的数据,并相信数据是有效的,因为数据是加密的。优点是不需要数据库调用来验证访问令牌。缺点是,一旦发出访问令牌,就没有简单的方法使其失效。如果您曾经想知道,当您可以增加访问令牌本身的过期时间时,为什么需要刷新令牌,这就是最大的原因

    需要注意的第二件事是SpringOAuth实现使用会话跟踪用户在身份验证过程中的位置。如果你不小心,你可能会陷入一个“无止境循环”的场景。假设您有两台oauth服务器——服务器A和服务器B:

  • 用户转到需要身份验证的网页或服务,因此被重定向到“foo.com/oauth/authorize”。负载平衡器将此请求发送到服务器A
  • 由于服务器A没有任何会话信息来证明用户已经通过身份验证,因此它会将用户重定向到foo.com/oauth/login上的登录页面。重定向通过负载平衡器返回,由于负载平衡器以“循环”方式工作,因此这次它将请求发送到服务器B
  • 用户成功登录,因此会写入会话信息以跟踪此情况只有服务器B知道此会话信息
  • 由于登录成功,用户被重定向回“foo.com/oauth/authorize”继续身份验证过程。重定向再次通过负载平衡器返回。由于负载平衡器以“循环”方式工作,这次它将请求发送到服务器a。但是服务器a不知道服务器B上发生的成功登录。请返回步骤2 可能解决这个问题的最佳(当前)解决方案是确保负载平衡器支持“粘性会话”——也就是说,一旦它将特定用户发送到服务器a或服务器B,它总是将该用户发送到同一服务器一段时间


    更好的解决方案可能是oauth实现完全不使用会话。相反,使用作为参数传递给/oauth/*的加密数据,该参数指示您在登录过程中的位置。与JWT令牌的工作方式类似,如果所有服务器都共享加密密钥,则所有服务器都可以信任该信息。

    专门用于实现授权码授权,以使用Redis中存储的Spring会话中的负载平衡器I。Redis是一种共享资源,确保Spring会话信息在Spring应用程序的所有运行实例之间共享

    有一种替代方法,通过在负载平衡器处部署粘性会话进行评估,这也是一个不错的选择,但此实现需要会话复制以维护HA

    粘性会话

    IMHO集中式缓存存储实现在应用程序端提供了更多的控制,应用程序中的所有配置都具有保证的HA,而无需任何额外开销

    带缓存存储

    下面是会话在Redis中的存储方式

    应用程序属性

    spring.session.store-type=redis
    server.servlet.session.timeout=3600s
    spring.session.redis.flush-mode=on-save
    spring.session.redis.namespace=spring:session
    
    用于覆盖HttpSession管理

    @Configuration
    @EnableRedisHttpSession
    public class HttpSessionConfig extends AbstractHttpSessionApplicationInitializer {
    
    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
    
        ...
        JedisConnectionFactory jedisConFactory = new JedisConnectionFactory(redisConfig);
        ...
        return jedisConFactory;
    
        }
    }
    
    我很肯定公司