5

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

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

这是此问题的工作流程:

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

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

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

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

4

3 回答 3

6

我对这个问题有点晚了,但也许这会帮助有人寻找类似的答案。在负载均衡器上使用多个 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 令牌的工作方式类似,如果所有服务器都共享加密密钥,则所有服务器都可以信任该信息。

于 2017-05-11T16:04:11.340 回答
3

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

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

于 2014-01-06T23:53:01.470 回答
1

特别是为了实现授权代码授权以使用负载均衡器,我将 Spring 会话存储在 Redis 中。Redis 是一种共享资源,可确保 Spring 会话信息在 Spring 应用程序的所有运行实例之间共享。

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

粘性会话 粘性会话

恕我直言,集中式缓存存储实现在应用程序端提供了更多控制,在应用程序中具有保证 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;

    }
}
于 2019-08-28T05:01:02.467 回答