如何使Spring Security OAuth2与负载均衡器配合使用?

时间:2013-09-16 20:08:21

标签: java spring spring-security oauth-2.0

我们目前有4个Spring应用程序使用Spring Security Oauth2项目进行身份验证。这些应用程序是REST API,由我工作的公司中的其他内部应用程序使用。

由于我们没有进行负载平衡,所以在开发和QA环境中一切都运行良好,现在我们正在进行预生产,我们正面临负载均衡器(LB)的问题。

这是此问题的工作流程:

  1. 客户端发送oauth令牌的请求
  2. LB将请求重定向到Box 1
  3. 框1验证并返回有效的承载令牌
  4. 客户收到令牌并将其存储以便通过sesion
  5. 使用
  6. 客户端在REST API中发送服务请求,将先前检索到的令牌添加到标题
  7. LB将请求重定向到Box 2
  8. Box 2无法进行身份验证,因为它无法识别令牌并返回Invalid Credentials响应
  9. 我们正在使用内存用户存储:

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

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

3 个答案:

答案 0 :(得分:3)

所有身份验证服务器和所有资源服务器必须共享相同的tokenStore才能验证令牌。

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

答案 1 :(得分:3)

我对这个问题有点迟了,但也许这会帮助搜索类似答案的人。在负载均衡器上使用多个oauth服务器时,需要注意两件事:

正如@ chris-h在他的回答中提到的那样,您需要确保任何其他oauth服务器都可以读取(和信任)任何oauth服务器发出的访问令牌的信息。您可以按照他的建议使用JDBC令牌存储,但这样做的缺点是,如果服务器A必须验证服务器B发出的访问令牌,它总是必须命中数据库才能这样做。

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

要注意的第二件事是Spring oauth实现使用会话来跟踪用户在身份验证过程中的位置。如果你不小心,你可以在一个无限循环中结束#34;场景。假设您有两个oauth服务器 - 服务器A和服务器B:

  1. 用户转到需要身份验证的网页或服务,因此会被重定向到&#34; foo.com/oauth/authorize"。负载均衡器将此请求发送到服务器A.
  2. 由于服务器A没有任何会话信息证明用户已经过身份验证,因此会将用户重定向到foo.com/oauth/login上的登录页面。重定向通过负载均衡器返回,并且由于负载均衡器正在循环使用&#34;时尚,这次它将请求发送到服务器B。
  3. 用户成功登录,因此会写入会话信息以跟踪此情况。 此会话信息仅为服务器B 所知。
  4. 由于登录成功,用户将被重定向回&#34; foo.com/oauth/authorize"继续身份验证过程。重定向再次通过负载均衡器返回。由于负载均衡器正在进行循环运行&#34;时尚,这次它将请求发送到服务器A.但是服务器A不知道服务器B上发生的成功登录。返回步骤2!
  5. 此问题的最佳(当前)解决方案可能是确保您的负载均衡器支持&#34;粘性会话&#34; - 也就是说,一旦它将特定用户发送到服务器A或服务器B,它总是将该用户暂时发送到同一服务器。

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

答案 2 :(得分:0)

专门用于实现授权代码授予以与负载均衡器一起工作,我将Spring会话存储在Redis中。 Redis是共享资源,可确保Spring会话信息在所有正在运行的Spring应用程序实例之间共享。

还有一种替代方法,可以通过在负载均衡器处设置粘性会话来评估,这也是一个不错的选择,但是此实现需要会话复制才能维护HA。

粘性会话 Sticky Sessions

IMHO集中式高速缓存存储实现可在应用程序端进行更多控制,使应用程序具有所有配置,并保证HA,而无需任何额外开销。

具有缓存存储区 With Cache Store

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

application.properties

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;

    }
}