认证和鉴权

认证鉴权是应用系统的基础需求,在微服务结构中服务分散,通常在 API 网关中进行身份验证和鉴权。当用户身份认证授权后,每次外部 API 的请求集中在网关中进行验证鉴权,不仅性能好,维护方便,还能让其余服务更加专注自己的业务。

API 网关的基本验证鉴权流程:

  • 登录身份验证时,通过 API 网关到认证服务中进行认证。
  • 认证通过后,后续的外部受保护的 API 到网关后先进行验证是否认证,如果已认证,继续在网关中对该 API 鉴权,通过后才进行后续的业务服务。否则,中止请求,立即反馈失败信息,不会路由到下层应用服务。

微服务中常用的认证鉴权解决方案为 jwt 、共享 session、 网关 oauth2 这三种。我用 java 的 spring cloud、spring security 技术框架来分析介绍这三种方案, 因为 java 是业务应用中服务端最常用的开发语言,spring cloud是 java 中最普遍的微服务框架, 认证鉴权 spring security 也是 spring  中经常配套的安全认证框架。

JWT token 模式

JWT 数据结构有三段:第一段 Header 头部包含采用加密算法和类型;第二段payload 里可以包含用户基本信息和到期时间、发布人、发布时间等;第三段是对前两段内容的哈希签名,用来验证前两段内容是否被篡改。

        JWT token 认证流程 。用户认证通过后生成 JWT token 返回给客户端保存,客户端后面在访问受保护的 API 时携带该 JWT token, 在 API 网关使用服务端密钥对 JWT token 进行验证,验证通过后使用 JWT 携带的用户信息进行后续的鉴权。具体见下面的流程图:

JWT token 验证鉴权通过后,通过网关调用其它微服务 API 时,可以带上 token 所拥有的用户信息,不必去认证服务查询。

在实践中,在认证服务器上使用成熟的 jwt 库,生成 jwt 的 access token 和 refresh token。access token 有效期短,一般不超过 1 个小时,而 refresh token 则生命周期长, 两种都会保存在客户端。一般 API 访问都用 access token,当 access token 过期后使用 refresh token 访问认证服务获取新的有效的 access token。这样降低了 access token 泄露导致的风险,而 refresh token 使用频率很低,也就降低 refresh token 泄露的风险。客户端 rest API 请求中,携带 access token 位置一般都放在 http  head 头的 Authorization 中,值以Bearer 开头,加空格后就是 access token 内容 。 在 spring cloud apigateway 网关中,使用 spring security 改写验证方法,对 jwt 验证。验证通过后,根据 jwt 所带的用户和权限信息,生成 spring security 的所需的 UserDetails,然后根据 spring security 配置的 API 对应权限对本次 API 进行鉴权,这个鉴权是 spring security 框架自动完成的。

JWT  token 模式最大的好处,就是服务端不用缓存认证信息,都是存在客户端的, 服务端不用存储认证状态,也就不会存在微服务的分布式导致的认证状态的共享问题,从而减少服务端的资源消耗,在互联网高并发的应用中,这种认证状态的存储所占的资源是非常可观的。JWT 包含了充足的用户信息,这样在 API 网关认证后,无需请求其余服务来获取信息,避免了认证和用户信息的接口来回调用,提高了响应性。

JWT token 也有很多局限性,具体问题如下:

  • jwt 内容不能过多。如果太多不仅消耗网络传输资源,也会对 jwt 的验证对计算力的消耗加大。由于 http 协议 head 允许数据量是受限的,也会导致一些用户信息放不进 token 中,因此尽量在 jwt token 中只放入多数 API 所需的用户信息,偶尔需要更多信息的则可以在 API 网关或应用服务中去调用用户详情接口来补充。
  • jwt 内容是明文。明文会导致一些敏感数据不能放在 jwt token 中,但多数 API 需要该信息,就不得不调用用户服务 API 接口来获取, 这样就降低了 jwt  token 性能上的好处。 解决办法不要采用 jwt, 直接换成没有明文的加密后的 token,在服务端 的网关解密验证即可。这种不好的地方就是不能使用 jwt 所带的成熟的第三方库,得自己编写相关加密和验证方法,增加一定开发成本和安全性,自己加密的安全性可能没有第三方库考虑的成熟。
  • jwt 无法取消认证。jwt 的 access token  和 refresh token 一旦认证通过后,在没有超期的情况下一直有效,无法取消,因为后端是没有存储认证状态,也就无法改变其认证状态。在一些取消认证的场景就不适合了,例如,一个用户同时只能在一个地方登录,新登录挤掉旧登录的地方,或者只允许移动端和 pc 端只能各自同时登录一个。还有后台管理者要检测用户登录情况,并能手动取消某个用户的登录。这些应用场景,jwt token 模式是无法解决的。有的文章说把 token 存在服务端,验证时检查对应 token 的状态,这样就和 cookie session 方案原理相同,完全没有了 jwt token 的优势,与其这样不如直接使用 session 共享模式。

 session 共享模式

在传统的 B/S 应用中cookie session 是最常用的解决方案,它有完整成熟的机制和生态,在前后端开发上都很便利。cookie 带来的安全、跨域和扩展性问题,也都有对应的解决办法。在微服务架构中,要使用传统的 cookie session,就需要把 session 共享到相关的服务中去。

在实践中,在 spring cloud 框架中使用spring session 库配置 Redis 来存储分享 session。在认证服务应用中,认证时通过 spring security  把生成的 session 通过 spring session 库存入 Redis。 在 spring  cloud  apigateway 中验证时,通过 spring security 把 cookie 对应 session 从 spring session 的 存储 Redis 获取出来,不过值得注意在 MVC 和 Reactive 的 spring security 中对 session id 的编码不一致,Reactor 对 sessionId 进行 bases64 编码,所以在 spring cloud apigateway 的 spring security 中 sessionid 解析需要重新改写。

cookie  session 有完整的生态和丰富的功能。在 spring security 库中通过简单的配置就能实现同一用户在不同地方同时登录的数量和互踢策略,查询所有在线用户和剔除某个在线用户,自动维护 session 过期,还能轻松实现记住我功能,还有各种关于 cookie 的安全配置,例如,http only、crfs、跨域管理等。前端开发时,也不用做什么配置,通过浏览器自动就能实现 cookie 的设置和请求携带,如果是 app 客户端,也能通过编码保存和使用cookie。

session 共享模式也有一些局限性和一些对应的解决方案,具体如下:

  • session 占用服务端资源。为了维护用户登录状态,需要在服务端在 Redis 存储 session,当并发量很大时,这种 session 的存储也是一个不小的开销。还有每次访问受保护的外部 API 请求,都需要网络调用 redis 获取 session ,增加了一定延时和并发性。
  • session 共享存在单点故障。session 都存储在 Redis 里,一旦 Redis 出现问题,那么整个应用就无法正常使用。不过可以 Redis 部署成集群,在主 Redis 出现问题时,可以重新选举主 Redis,从而解决可靠性问题。
  • cookie 的局限性。cookie 虽然给 web 开发中带来很多便利,但经常因为跨域和扩展性也带来不少问题,虽然有一些解决方案,但有些场景较为复杂,需要多方配合才能解决,还有些场景禁用 cookie。为了避免 cookie 的局限性,可以把 sessionId 放在 http 的其他 head 字段,这样就把 sessionId 当作普通 token 来使用,不受 cookie 的局限。在 spring security 中配置HeaderWebSessionIdResolver 代替默认 CookieWebSessionIdResolver, 这样可以把 sessionId 的值存入 http 的头部SESSION 字段 ,当然这个SESSION 字段可以在HeaderWebSessionIdResolver 中设置成别的。也可以自己继承实现WebSessionIdResolver 把 sessionId 放在 url 的 query 参数中。

网关 OAuth2 模式

 OAuth 2.0 是目前最流行的一种授权机制,用来授权第三方应用,获取用户数据,实现 SSO 单点登录。很多应用需要对接第三方登录,比如微信登录、 QQ 登录、微博登录、 Google 账号登录、github 授权登录等等,这些都是典型的 OAuth2 使用场景。或者自己应用系统庞大,需要自己提供 OAuth2 的认证授权服务,供子应用或第三方应用对接,实现单点登录。实际OAuth2 只有授权功能,要扩展认证功能,需要 OIDC,它对 OAuth2 进行扩展,通过扩展的ID Token字段,提供用户基础身份信息。 OAuth2 定义了四种角色,授权服务器 (Authorization Server)、资源服务器 (Resource Server)、客户端 (Client)、资源拥有者 (Resource Owner)。

在复杂系统中有多个子系统,每个子系统需要统一的认证授权和用户信息,OAuth2 在该场景中的具体解决方案如下。 首先, OAuth2 的授权服务器在单独的一个应用中 通过 Spring Authorization Server 框架来实现,供其他子系统访问。然后,在每个子系统的 API 网关中集成 OAuth2含客户端、 spring security,可以让每个受保护的 API 请求直接在网关中完成,无需远程调用其它服务,这样性能更好,内聚性更高。 当资源拥有者通过网关的 OAuth2 的客户端向OAuth2 的授权服务器认证授权成功后,会获得 OAuth2 的 token,并把该 token  缓存起来,如果前后端使用的 cookie session 方案,那么 OAuth2 的 token 就存储在 session 当中。后续当前端每次访问受保护的 API 时,会在网关中从 http API 所带的 cookie 解析出对应 session,然后从 session 中得到 OAuth2 的 token。网关通过解析token 获得用户信息和权限信息,如果信息量不够,用该token 向  Authorization Server 端获取更多信息,从而适配成客户端所需的userDetail,从而通过userDetail中权限保护本次 API 访问请求。这些具体实现在网关中采用spring security 就行,通过一些 OAuth2 的配置就可以自动完成上面的流程。如果不愿意采用 cookie,那么可以用上面说过的 sessionId 设置到 http 的其他 head 字段中 。如果网关会有多个实例部署,那么在网关中就需要采用 spring session 来实现共享 session。

在对接微信登录、 QQ 登录、微博登录的场景,只是为了方便用户登录,它们提供的OAuth2 的授权服务和用户信息都是远远不够的,所以该系统必须有自己的更多的用户信息和权限管理。在这种场景下,用户认证服务在 spring security 中配置 OAuth2 client 就行,只用于获取对接的第三方登录的用户基本信息,并且也只登录使用一次,后续访问中不再需要。登录时通过OAuth2 client 获取到 user id,然后根据该 user id 找到本系统中该用户详情和授权信息。 

结束

根据现在主流微服务架构,验证鉴权的解决方案优先推荐JWT token 模式,其次是共享 session 。如果要实现多个应用系统的单点登录,用户信息和验证授权的集中管理,OAuth2 是目前最主流的。OAuth2 既有 jwt 那种无状态的模式,也有控制登录实时有效性的有状态的模式,从而能满足各种场景。上面的鉴权主要是对 常规 api 的访问控制,如果要对多媒体资源(文档、pdf、音视频等)的访问控制,就需要更合适的解决方案,具体后续再说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值