当前位置: 首页 > 科技观察

如何在微服务中进行身份验证?

时间:2023-03-17 13:00:43 科技观察

最近有朋友在微信上问这个问题,松哥来和大家聊聊,这篇主要是和大家聊聊思路,没有写代码,朋友们可以结合之前的文章,应该可以把代码写出来这篇文章自己。当然,这个想法只是我自己的实践经验,不一定是最完美的方案。欢迎小伙伴们在留言中讨论。1.认证与授权首先小伙伴们都知道,无论我们在Shiro还是SpringSecurity中实现什么功能,都有两个核心功能:认证和授权。因此,我们处理微服务中的认证问题也可以从这里两个方面来考虑。1.1AuthenticationAuthentication说白了就是登录。传统的web登录是Cookie+Session的方案,依赖于服务器本地内存。在微服务中,由于服务数量众多,这种方案显然已经不适用了。可能有朋友会说使用Redis+SpringSession进行Session共享是一种方案,但并不是最好的方案,因为这种方案的性能和扩展性都比较差。因此,在微服务中推荐使用token进行认证。可以选择JWTtokens,这是目前广泛使用的方案。但是熟悉JWT的朋友都知道,纯无状态登录是无法实现注销的,很头疼。因此,在实际应用中,单纯使用JWT是不够的。一般需要结合Redis对生成的JWT字符进行转换。字符串也保存在Redis上,并设置了过期时间。在判断用户是否登录时,需要去Redis中查看JWT字符串是否存在。如果存在,则解析JWT字符串。问题,如果不能解析成功,说明token不合法。这种有状态登录+无状态登录混合的方式,看起来有点不伦不类,但目前来看,这种折中方案是可行的。其实上面的方案,说白了,和传统的Cookie+Session没什么区别。思路几乎完全照搬:传统的Session被Redis取代;传统的在服务器和浏览器之间穿梭的jsessionId被JWT字符串代替;传统的jsessionId通过cookies传输,当前的JWT由开发者手动设置后通过请求头传输;传统的Session可以自动更新,现在JWT是手动更新的。查看Redis上token的过期时间,如果快过期了,就重新设置一下,其他完全一样。这是认证方案的选择。1.2授权微服务中的授权也可以使用Shiro或者SpringSecurity框架来完成,比较省事。考虑到微服务技术栈是Spring家族的产物,建议大家在权限框架中选择SpringSecurity(如果有不熟悉SpringSecurity的小伙伴可以在后台回复ss微信♂,有教程)。当然,如果你觉得SpringSecurity比较复杂,想自己动手,也是可以的。如果自己动手,也可以使用SpringSecurity的思想。松哥最近的项目是这样的:请求到达微服务后,先找到当前用户的各种信息,包括当前用户的角色和权限。然后存放在当前线程绑定的ThreadLocal对象中。另一方面,自定义权限注解和角色注解,在切面解析这些注解,检查当前用户是否有需要的角色/权限等。当然,如果你使用SpringSecurity,就不需要自定义注解以上,直接使用SpringSecurity自带的就可以了,你也可以在SpringSecurity中体验到更丰富的安全特性。2.认证服务那么在哪里做认证和授权呢?让我们先谈谈身份验证。认证可以分为两步:登录验证2.1登录一般来说,我们可以为登录做一个认证服务。当登录请求到达网关时,我们将其转发给认证服务以完成认证操作。在认证服务上,我们检查用户名/密码是否OK,用户状态是否OK。如果没有问题,生成JWT字符串,同时将数据存入Redis,然后返回JWT字符串。如果系统有注册功能,注册功能也是在这个微服务上完成的。2.2验证验证是指在每个请求到达时验证用户是否已经登录。当然这个可以和2.1一起做,但是松哥不推荐。问题是如果是创建订单的请求,这个请求本来是要通过网关转发给订单服务的。但是,此时必须在网关上调用2.1小节的服务进行登录验证,然后再转发给订单服务,这样做显然是非常麻烦和不合理的。更好的方式是直接在网关上验证请求的token是否合法。这个验证本身是比较容易的。验证token是否合法,我们只需要检查Redis上是否存在token,而JWT命令只要能顺利解析出卡片,就可以在网关上进行这个操作。以Gateway为例,我们可以自定义全局过滤器,在全局过滤器中验证每个请求的token。验证通过则转发请求,否则不转发。验证通过后,转发到具体的微服务后,我们就可以把解析出来的用户id和用户名等信息放到请求头中,然后再转发,这样到了每个具体的微服务之后,我们就知道请求是谁了发送了,这个人有什么角色/权限,方便下一步权限验证。松哥的做法是定义一个公共模块,所有的微服务都依赖于这个公共模块,在这个公共模块中定义了一个拦截器,它会拦截每一个请求,从请求头中取出用户ID,然后从Redis中获取到具体的用户信息,并存储在ThreadLocal中,这样在后续的方法调用中,如果需要判断用户是否具有某种权限,可以通过ThreadLocal获取。大致是这样的过程。3、授权服务授权不能在网关上做,必须在每个微服务上做。对微服务的授权大致可以分为两类:前端发送的请求(外部请求)。其他微服务发送的请求(内部请求)。3.1外部请求对于外部请求,按照正常的权限验证处理即可。自定义注解或者使用SpringSecurity等框架都是可以的。如果是自定义注解,可以结合AOP,自己定义切面来处理权限注解,当然这些功能基本每个微服务都需要,所以可以抽出来一个public模块,可以依赖在不同的微服务中。3.2内部请求对于内部请求,一般不需要认证,可以直接处理内部请求。问题是如果你使用OpenFeign,数据是通过接口暴露出来的。如果你不认证,你会担心从外部请求调用这个接口。针对这个问题,我们也可以自定义注解+AOP,然后在请求内部调用的时候,额外增加一个header字段来区分。当然,当内部请求到达微服务时,也需要进行鉴权,就像请求从网关转发到各个具体的微服务时一样,需要进行鉴权,但是显然,我们不需要使用OpenFeign每次调用其他服务时不时传递一堆认证信息,我们可以通过实现feign.RequestInterceptor接口定义一个OpenFeign请求拦截器,在拦截器中,统一设置OpenFeign请求的请求头信息。那么,关于微服务中的身份验证,我们目前正在这样做。欢迎小伙伴们留言一起讨论。