单体应用到分布式再到微服务,用户认证和授权机制的演进史
Web应用和用户的身份验证息息相关,从单一服务器架构到分布式服务架构再到微服务架构,用户安全认证和授权的机制也一直在演进,下文对各个架构下的认证机制做个总结。
单一服务器架构
该架构下后端只有一台服务器提供服务。
- 认证授权流程:
1.Web应用中设置拦截器对所有请求进行拦截,如果校验不通过则跳转登陆重新认证
2.客户端发起认证请求,传入用户名密码
3.通过验证后,应用在服务器上将用户信息存入session中,并将session id返回给客户端
4.客户端将session id存在本地cookie或local storage中,再次访问时传入session id
5.Web应用根据session id对比服务器的session数据,确认用户身份
- 适合场景
这种模式只适合单服务器的场景
- 常用实现
shiro ;自定义注解 + 拦截器/AOP方案;filter方案等
- 缺陷
如果是分布式服务或跨域体系架构的系统则会出现session无法共享的问题。
- 如何解决
解决方案有两种,第一种是将session统一存放,实现分布式session共享,第二种方案是客户端生成token
分布式服务架构
分布式服务架构下,后端的服务器由一台变成了多台。
Session共享方案
- 认证授权流程:
1.使用nginx做负载均衡,多台web应用
2.客户端发起认证请求,根据策略到其中一台web
3.通过验证后,服务端将用户的信息存入持久化层,例如redis缓存数据库,再生成token令牌
4.并将生成的token返回客户端,存入客户端缓存。
5.客户端再次访问web,根据策略路由到其中一台,web应用查询持久化层,根据带入的token查询用户登陆信息,确认身份。
- 适合场景
并发量不高的分布式应用
- 常用实现
shiro ;自定义注解 + 拦截器/AOP方案;filter方案等
- 缺陷
1、这种方案的缺陷在于依赖于持久层的数据库如redis,会有单点风险,如果持久层失败,整个认证体系都会挂掉;
2、每一次调用都需要访问持久化层进行验证,会给持久化层造成压力,在高并发场景,持久化层容易成为瓶颈;
- 如何解决
持久层的数据库如redis做高可用和集群。
客户端token方案
- 认证授权流程:
1.客户端发起认证请求,根据策略到其中一台web
2.通过验证后,服务端将用户登陆信息封装成token(token本身可以自解释)返回给客户端,不存储在服务端
3.客户端将返回的完整信息存入缓存。
4.客户端再次访问web,根据策略路由到其中一台,带入登陆信息,服务端根据信息确认身份。
- 适合场景
优势在于服务端不保存用户会话数据,服务端无状态,不用去持久层查询从而增加了效率,适合一次性验证、restful api 的无状态认证、并发量较高的分布式应用等
- 常用实现
jwt
- 缺陷
token一旦下发便不受服务端控制,如果发生token泄露,服务器也只能任其蹂躏,在其未过期期间不能有任何措施,同时存在以下两个问题:
1、失效问题,因为token是存放在客户端的,服务端无法主动让token失效,比如踢人下线、用户权限发生变化等场景就实现不了
2、续签问题,token 有效期一般都建议设置的不太长, token 过期后用户需要重新登录,导致用户需要频繁登录
- 如何解决
针对失效问题:
① 将 token 存入内存数据库:将 token 存入 DB 或redis中。如果需要让某个 token 失效就直接从 redis 中删除这个 token 即可。但是,这样会导致每次使用 token 发送请求都要先从redis中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则,不可取。
② 黑名单机制:使用内存数据库比如 redis 维护一个黑名单,如果想让某个 token 失效的话就直接将这个 token 加入到 黑名单 即可。然后,每次使用 token 进行请求的话都会先判断这个 token 是否存在于黑名单中。
针对续签问题:
① 类似于 Session 认证中的做法: 假设服务端给的 token 有效期设置为30分钟,服务端每次进行校验时,如果发现 token 的有效期马上快过期了,服务端就重新生成 token 给客户端。客户端每次请求都检查新旧token,如果不一致,则更新本地的token。这种做法的问题是仅仅在快过期的时候请求才会更新 token ,对客户端不是很友好。每次请求都返回新 token :这种方案的的思路很简单,但是,很明显,开销会比较大。
② 用户登录返回两个 token :第一个是 acessToken ,它的过期时间比较短,不如1天;另外一个是 refreshToken 它的过期时间更长一点比如为10天。客户端登录后,将 accessToken和refreshToken 保存在客户端本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果 refreshToken 有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录即可。
说明:JWT 最适合的场景是不需要服务端保存用户状态的场景,但是如果考虑到 token 注销和 token 续签的场景话,没有特别好的解决方案,大部分解决方案都给 token 加上了状态,这就有点类似 Session 认证了。
微服务架构
微服务架构下服务端根据业务拆分为多个服务,除了公司内部服务外,外部客户或者第三方也通过api网关统一转发请求,在这个阶段系统需要提供跨系统单点登录、第三方授权登录等基础能力。在这种架构下后端建立单独的认证授权中心。 目前实现统一身份认证和授权的技术手段较多,总体可以归纳为以下几类。
传统的 Cookie + Session 解决方案 ,有状态会话模式
- 认证授权流程:
同分布式服务架构中的“Session共享方案”
- 适合场景:
同分布式服务架构中的“Session共享方案”
- 常用实现:
同分布式服务架构中的“Session共享方案”
- 缺陷:
分布式 Session 是老牌的成熟解决方案,但因其状态化通信的特性与微服务提倡的API导向无状态通信相互违背,且共享式存储存在安全隐患,因此微服务一般不太采用
JWT+网关撤销令牌方案,无状态交互模式
- 认证授权流程:
同分布式服务架构中的“客户端token方案”,只是在该基础上,加上API网关令牌失效和令牌续签的机制,参考分布式服务架构中的“Session共享方案”的“如何解决”部分。
- 适合场景:
同分布式服务架构中的“客户端token方案”
- 常用实现:
同分布式服务架构中的“客户端token方案”
- 缺陷:
同分布式服务架构中的“客户端token方案”
JWT + OAuth2.0 +CAS方案
CAS是单点登录的解决方案,单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。CAS共涉及角色有:CAS Client(下文提到的系统A等)、User、CAS Server(认证中心)。CAS 是一个认证框架,其本身定义了一套灵活完整的认证流程,但其兼容主流的认证和授权协议如 OAuth2、SAML、OpenID 等,因此一般采用 CAS + OAuth2 的方案实现 SSO 和授权登录。
- 认证授权流程:
1.用户访问系统A;
2.系统A发现用户没有登录,也没有ticket,重定向到认证中心;有ticket跳转 第7步;
3.认证中心发现用户并未登录,展示登录页面;
4.用户登录;
5.认证中心登录成功,带着生成的ticket,重定向到之前的系统A页面;
6.系统A检查登录,还是未登录,但存在ticket。系统A带着ticket和认证中心进行校验;
7.认证中心返回对应的用户名;
8.系统A检查返回的用户名是否只有一个且不为空,若是,则返回给用户指定的资源信息;若不是,则跳转 第2步。
- 常用实现
spring security + CAS
OAuth2.0协议考虑到了微服务认证中的方方面面,提供的多种授权模式。这种方案的优点是安全性好,是业内成熟的给第三方提供授权登录解决方案,但是实现成本和复杂度高。 OAuth2.0提供了4种授权模式,能够适应多种场景,作为基于令牌的安全框架,可以广泛用于需要统一身份认证和授权的场景。 在 OAuth2.0 的实施过程中,一般会采用 JWT 作为令牌的主要标准。以文章分享举个例子,你在(登录后)浏览某论坛时,遇到了一篇好文章,想要分享给大家时,你需要登录(扫码或者登录用户名密码)第三方账户如微信来完成分享。
OAuth2.0还有个替代方案,OIDC,它是OpenID Connect的简称,OIDC=(Identity, Authentication) + OAuth 2.0。它在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议。暂时没研究,有兴趣的朋友可以查查。
- 认证授权流程:
1、在点击分享链接时,主服务器会直接(接口)请求第三方服务器,第三方服务器会校验主服务器上的用户此时是否授权给自己(第三方);
2、没有授权信息,用户登录(扫描或者密码)。
3、主服务器拿着用户的登录信息去第三方确认认证授权(密码是否正确、是否授权)。
4、认证失败,密码错误。(重新登录)
5、认证成功(确认授权),第三方认证服务会返回的授权码。
6、主服务器带着授权码请求第三方资源服务器,第三方资源服务器会返回指定的URI(分享界面)。
7、用户操作分享操作(填写自己的话语),点击分享按钮完成。
- 常用实现
spring security +OAuth2.0
OAuth2.0提供了4种授权模式,如下:
- 授权码模式
授权码模式相对其他三个模式来说是功能最完整,流程最安全严谨的授权方式。它的特点是通过客户端的后台服务器与服务提供商的认证服务器进行交互:
A. 用户访问客户端,客户端将用户导向认证服务器,需要携带客户端ID凭证和重定向URI。
B. 用户选择是否给予客户端授权。
C. 假设用户给予授权,认证服务器将用户导向事先指定的重定向URI,同时附上一个授权码。
D. 客户端收到授权码后,携带事先指定的重定向URI和授权码向认证服务器申请令牌。
E. 认证服务器核对授权码和重定向URI,确认无误后,向客户端颁发访问令牌(access token)和刷新令牌(refresh token)。
这里的resource owner代表客户,user-agent代表客户使用的访问工具如浏览器或App,client指第三方客户端
- 简化模式
简化模式不通过服务端程序来完成,比授权码模式减少了“授权码”这个步骤,直接由浏览器发送请求获取令牌,令牌对访问者是可见的,且客户端不需要认证,这种模式一般用于无后端应用,如手机/桌面客户端程序、浏览器插件
A. 用户访问客户端,客户端将用户导向认证服务器,需要携带客户端ID凭证和重定向URI。
B. 用户选择是否给予客户端授权。
C. 假设用户给予授权,认证服务器将用户导向事先指定的重定向URI,并在URI的Hash部分包含了访问令牌(Fragment)。
D. 浏览器向资源服务器发出请求,其中不包含事先收到的Hash部分(Fragment)。
E. 资源服务器返回一段脚本,其中包含的代码可以获取Hash部分中的令牌。
F. 浏览器执行事先获取的脚本,提取出令牌
G. 浏览器将令牌发送给客户端。
- 密码模式
密码模式中,用户向客户端提供用户名和密码,客户端使用这些信息,直接向认证服务器索要授权。这种模式违背了前面提到的微服务安全要解决的问题(不暴露用户名和密码),但是在一些用户对客户端高度信任的情况下,例如公司内部软件间的授权下,使用这种模式也是适用的
A. 用户向客户端提供用户名和密码。
B. 客户端将用户名和密码发送给认证服务器,向认证服务器索要令牌。
C. 认证服务器确认无误后,向客户端提供访问令牌。
- 客户端模式
客户端模式是客户端以自己的名义去授权服务器申请授权令牌,并不是完全意义上的授权。一般不适用这种模式
A. 客户端向认证服务器进行身份认证,并要求获取访问令牌。
B. 认证服务器确认无误后,向客户端提供访问令牌。
- 刷新令牌
如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌
A. 客户端向认证服务器进行身份认证,并要求获取访问令牌。
B. 认证服务器确认无误后,返回访问令牌和一个刷新令牌。
C. 客户端通过访问令牌访问受保护资源。
D. 如果访问令牌未过期,则向客户端提供资源服务。
E. 客户端通过访问令牌访问受保护资源。
F. 如果访问令牌过期,受保护资源服务器返回Invalid Token Error。
G. 客户端得到上方的错误后,通过刷新令牌向授权服务器申请一个新的访问令牌。
H. 认证服务器确认无误后,返回访问令牌和一个刷新令牌。