Web应用和用户的身份验证息息相关,从单一服务器架构到分布式服务架构再到微服务架构,用户安全认证和授权的机制也一直在演进,下文对各个架构下的认证机制做个简单介绍。
1.Web应用中设置拦截器对所有请求进行拦截,如果校验不通过则跳转登陆重新认证
2.客户端发起认证请求,传入用户名密码
3.通过验证后,应用在服务器上将用户信息存入session中,并将session id返回给客户端
4.客户端将session id存在本地cookie或local storage中,再次访问时传入session id
5.Web应用根据session id对比服务器的session数据,确认用户身份
这种模式只适合单服务器的场景,如果是分布式服务或跨域体系架构的系统则会出现session无法共享的问题,解决方案有两种,第一种是将session统一存放,实现分布式session共享,第二种方案是客户端生成token
1.使用nginx做负载均衡,多台web应用
2.客户端发起认证请求,根据策略到其中一台web
3.通过验证后,服务端将用户的信息存入持久化层,例如redis缓存数据库,再生成token令牌
4.并将生成的token返回客户端,存入客户端缓存。
5.客户端再次访问web,根据策略路由到其中一台,web应用查询持久化层,根据带入的token查询用户登陆信息,确认身份。
这种方案的缺陷在于依赖于持久层的数据库或者文件系统,会有单点风险,如果持久层失败,整个认证体系都会挂掉。所以使用客户端token的方案可以规避这一风险。
1.客户端发起认证请求,根据策略到其中一台web
2.通过验证后,服务端将用户登陆信息返回给客户端,不存储在服务端
3.客户端将返回的完整信息存入缓存。
4.客户端再次访问web,根据策略路由到其中一台,带入登陆信息,服务端根据信息确认身份。
此方案的优势在于服务端不保存用户会话数据,服务端无状态,不用去持久层查询增加了效率,目前此方案使用比较多的协议是JWT,下面介绍一下
JSON Web Token,认证流程如下:
1.客户端请求登陆,传入用户名密码
2.服务端验证通过后,创建一个JWT格式的JSON对象,并返回给客户端
3.客户端将JWT在本地存储
4.客户端再次请求时,将JWT放入HTTP请求头
5.服务端从HTTP头中取出JWT并校验,确认用户身份
JWT格式
JWT构成有三部分:header(头部),payload(荷载),signature(签名),每一个部分都是一个JSON对象
Header:
JWT的头部主要用于描述关于该JWT的最基本的信息,例如其类型以及签名所用算法等
{
“type”: “JWT”,
“alg”: “HS256”
}
这里头部说明了是一个JWT,签名算法时HS256
Payload:
荷载是存放有效信息的地方,是JWT的主体内容,即存放用户登陆信息,JWT定义了标准的字段有七个:
iss:JWT 签发者
sub:JWT 所面向的用户
aud:接收 JWT 的一方
exp:JWT 的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之前,该 JWT
都是不可用的
iat:JWT 的签发时间
jti:JWT 的唯一身份标识,主要用来作为一次性 token, 从而回避重放攻击。
除了标准字段还可定义私有字段:
例如:
{
“sub”: “10001”,
“name”: “leon”,
“admin”: true
}
Signature:
为了防止用户篡改数据,将对上面两个部分的数据签名,通过指定的算法(也就是头部中声明的算法)将两个数据用Base64URL算法转化为字符串,然后再根据指定secret(密钥)加密生成签名,公式如下:
HMACSHA256(base64UrlEncode(header) +
“.” + base64UrlEncode(payload),
secret)
注意secret只能保存在服务器端,不能向外公开
那么签名是如何能够防止数据篡改呢?
服务端在接收到JWT后,会首先对头部和荷载内容再做一次签名(使用头部中声明的算法),然后再和JWT中的签名做对比,如果有人篡改了两部分的内容,并重新编码生成新的签名,那么匹配将不成功,服务端将拒绝请求
客户端token的方案安全性好,效率高,但是存在身份验证注销的问题,因为服务端不存会话信息,所以在服务的使用过程期间是无法取消token或更改token权限的,一旦JWT签发,有效期内将会一直有效,所以建议建立JWT时有效期设置短一些。
微服务架构下服务端拆分多个服务,外部客户或者第三方通过api网关统一转发请求,在这种架构下后端建立单独的认证授权中心,在外部发起登陆请求时统一校验,并生成token返回客户端,客户端再访问每一个微服务时都在HTTP头部带上token,每个微服务进行校验。实现此流程一般也有两种方案,OAuth2.0或JWT+网关撤销令牌
JWT的流程和上述流程基本一致,再加上API网关撤销令牌的机制,撤销的方案也有两种:
1.API网关维护一个已撤销令牌的黑名单,对所有经过的JWT进行对比,如果在黑名单中则拒绝请求。
2.服务端建立令牌库,例如将JWT存入redis中,API网关在转发前先去库中查询令牌是否撤销或过期。
JWT+网关方案的优点是简便轻巧
OAuth2.0协议考虑到了微服务认证中的方方面面,提供的多种授权模式。这种方案的优点是安全性好,但是实现成本和复杂度高
OAuth2.0的四种授权模式:
授权码模式
授权码模式相对其他三个模式来说是功能最完整,流程最安全严谨的授权方式。它的特点是通过客户端的后台服务器与服务提供商的认证服务器进行交互:
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. 认证服务器确认无误后,返回访问令牌和一个刷新令牌。