如何使 Spring Security OAuth2 与负载均衡器一起使用?

ras*_*orp 5 java spring spring-security oauth-2.0

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

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

这是此问题的工作流程:

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

我们正在使用内存用户存储:

<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />
Run Code Online (Sandbox Code Playgroud)

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

Alv*_*son 5

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

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

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

要注意的第二件事是 Spring oauth 实现使用会话来跟踪用户在哪里进行身份验证。如果您不小心,您可能会陷入“无限循环”的场景。假设您有两个 oauth 服务器——服务器 A 和服务器 B:

  1. 用户转到需要身份验证的网页或服务,因此被重定向到“foo.com/oauth/authorize”。负载均衡器将此请求发送到服务器 A。
  2. 由于服务器 A 没有任何会话信息来证明用户已经通过身份验证,因此它将用户重定向到 foo.com/oauth/login 上的登录页面。重定向通过负载均衡器返回,并且由于负载均衡器以“循环”方式工作,这次它将请求发送到服务器 B。
  3. 用户登录成功,因此写入会话信息以跟踪此情况。此会话信息只有服务器 B 知道
  4. 由于登录成功,用户被重定向回“foo.com/oauth/authorize”以继续身份验证过程。重定向再次通过负载平衡器返回。由于负载均衡器以“循环”方式工作,这次它将请求发送到服务器 A。但服务器 A 不知道服务器 B 上发生的成功登录。返回第 2 步!

这个问题的最佳(当前)解决方案可能是确保您的负载均衡器支持“粘性会话”——也就是说,一旦它将特定用户发送到服务器 A 或服务器 B,它总是将该用户发送到同一服务器一会儿。

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


Chr*_* H. 3

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

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