使用 Redis 的微服务中用户会话的差异化方法
英文阅读:点击这里
您决定将旧的、过时的单体架构分解为更现代、更高效的微服务架构。肯定会出现的第一个问题来自这样一个事实,即拥有分布式架构意味着您的新小块不再捆绑在一起,它们将需要通过服务相互通信并且不再能够共享内存区域.好吧,事实证明,您的应用程序中最重要的数据之一保存在内存中并在应用程序的所有模块之间共享:用户会话。
当您快速搜索微服务中的身份验证和授权时,某种技术似乎是最好的(如果不是唯一的)解决方案:JWT。基本上,这种方法建议您将所有会话数据放入签名和加密的哈希(令牌)中,并将其发送回登录到您的应用程序的客户端。然后,对于每个请求,客户端必须发回这个令牌(通常在请求头中),然后我们可以验证这个令牌的真实性并从中提取会话数据。有了手头的数据,我们可以将其发送到任何需要它的服务,直到请求得到答复。我们可以上传一个无服务器函数来解密和验证令牌签名,甚至可以将此任务委托给我们的 API 网关。
乍一看,它看起来非常漂亮和优雅,但等一下......这看起来比以前复杂得多,对吧?让我们看看这在我们的单体应用上是如何工作的,以及为什么它必须如此剧烈地改变。
昔日
过去,HTTP 用户会话存储在服务器内存中,由随机生成的没有任何意义的哈希进行索引——甚至出现了术语“不透明令牌”来识别没有任何数据的令牌。然后这个无数据令牌被发送回浏览器,服务器请求它将这个令牌保存在Cookie中。
就其性质而言,Cookie 会随着每个后续请求自动发送回服务器,因此在用户登录后,对同一服务器的下一个请求肯定会包含 cookie,而后者又会包含检索它所需的令牌。服务器内存中的用户数据。
这种方法已经被各种企业级服务器(jboss、weblogic 等)使用了十多年,并且被认为是安全的。但是现在我们有了微服务,所以我们不能再依赖它了,因为每个服务都是相互隔离的,所以我们不再有一个公共区域来存储这些数据。
那么,正如所提议的那样,解决方案是将数据发送给客户,让客户负责保存这些数据,并在必要时将其发回。这带来了一系列我们以前没有遇到的问题,现在我将尝试解释其中的一些问题:
安全问题
大多数 JWT 实现建议您通过称为“授权”的 HTTP 标头将客户端令牌发送回服务器。为此,您的客户端需要能够接收、存储和检索令牌。问题是,如果客户端可以访问这些数据,那么任何恶意代码也可以。拥有令牌后,犯罪分子可以尝试对其进行解密以查看数据,或者只是使用它来访问应用程序。
在单体应用中,服务器只是将不透明的令牌发送到名为 SetCookie 的 HTTP 标头中的浏览器,因此客户端上的代码不必像浏览器那样自动处理信息。甚至 cookie 也可以设置为无法通过 javascript 访问,因此恶意代码永远无法访问。
性能问题
为了保证一切安全,您必须通过运行加密算法对令牌进行签名,这样任何人都无法更改其内容,并且还要对令牌进行加密以确保没有人可以轻松读取该内容。
此外,对于服务器上收到的每个请求,它必须运行令牌的解密,以及运行再次生成签名的加密算法以验证其有效性。我们都知道这些加密算法在计算上是多么昂贵,而且在我们的旧单体中这些都不是必需的,除了在登录期间比较密码时唯一的执行 - 但是在使用 JWT 时也需要执行此操作。
可用性问题
JWT 令牌在控制非活动会话到期方面不是很好。生成令牌时,它被认为是有效的,直到其到期日期,该日期存储在令牌本身中。也就是说,您要么为每个请求生成一个新令牌,要么生成另一个名为 Refresh Token 且有效期更长的令牌,并在您的令牌过期时仅使用它来获取新令牌。但是请注意,这只是将问题从一个地方转移到另一个地方 - 当刷新令牌过期时会话实际上会过期,除非您也刷新它。
如您所见,建议的解决方案带来了许多未解决的问题。但是,我们如何才能在微服务架构中实现有效且安全的用户会话管理呢?
将旧概念带入新世界
让我们关注真正的问题:用户会话过去存储在服务器内存中,许多企业服务器可以在集群内的各个实例之间复制这块内存,这使得它在任何情况下都可以访问。然而,我们今天几乎没有应用服务器,大多数微服务模块都是独立的 java / node / python / go /(选择你的技术)应用程序。那么它们将如何共享一块内存呢?
实际上很简单:创建一个中央会话服务器。
这里的想法是以与以前相同的方式存储用户的会话:您必须生成一个不透明的令牌以用作键,然后您可以添加尽可能多的数据以通过该键进行索引。我们将在您网络上的任何微服务都可以访问的中心位置执行此操作,因此当他们中的任何一个需要数据时,只需一个电话即可。
这项工作的最佳工具是Redis。 Redis 是一个使用键值对的内存数据库,延迟不到一毫秒。您的微服务将能够读取会话数据,就好像它直接存储在自己的内存中一样(嗯,几乎,但速度很快)。此外,Redis 有一个对我们的案例至关重要的功能:我们可以为键值对设置超时时间,一旦时间到期,该键值对就会从银行中删除。时间可以根据需要重新设置。听起来就像会话超时,对吧?
为了使所有这些工作,我们至少需要两个模块:
1 - 认证模块
您必须创建一个负责对用户进行身份验证的微服务。它将接收带有登录名和密码的请求,将检查密码是否有效,然后在 Redis 中创建会话。
为此,它将生成不透明的令牌,从您的关系数据库(或 nosql,具体取决于您的情况)中检索用户数据,然后使用令牌作为键将此数据存储在 Redis 中。然后,它会将密钥返回给请求登录的客户端,如果客户端是浏览器,最好在 SetCookie 标头中。
2 - 授权模块
我特别喜欢这个模块成为每个微服务的一个组成部分,但如果你愿意,你可以把它放在你的 API 网关中。它的职责是拦截每个发出的请求,检索不透明的令牌,然后转到 Redis 并检索会话数据,将此数据与请求一起传递给将处理它的服务。
[](https://res.cloudinary.com/practicaldev/image/fetch/s--j_Cyt7Fw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads .s3.amazonaws.com/i/titrfns0fv40oldlkou9.jpg)
结论
如您所见,这里提出的解决方案比使用 JWT 管理用户会话更简单、更快、更安全。但请记住以下几点:
-
如果您使用的是单个 Redis 分片,这可能是您的“单点故障”,即如果失败,您将无法登录。我建议使用更强大的生产设置,它们之间有更多的分片和数据复制。
-
会话数据可以被任何可以访问 Redis 的模块修改,所以使用“总是添加,永不删除”的方法,就像过去使用单体应用一样。
我希望这有帮助。
作为奖励,我在这里留下一个 SessionManager 类来帮助使用Jedis和 Tomcat 令牌生成器在 Java 中实现,这通常已经包含在 Spring Boot 中:
会话管理器
RedisClient
玩的很开心!
更多推荐
所有评论(0)