前面一篇我们了解了微服务安全认证架构是如何演进而来的,但是发现v2.5架构仍然较重,有没有轻量级一点的方法呢?其实业界早已有了实践,它就是基于JWT的安全认证架构。JWT到底是个什么鬼呢?本篇为你解答!
1、V2.5版本架构存在的问题
在v2.5版本Token+Gateway模式下,适合于大部分微服务场景,但是当网站流量很大的时候,对AuthService的访问压力也会比较大,它很可能会成为性能和扩展性的瓶颈!
MyShop v2.5版本:Token+Gateway
此外,对于很多对于安全不是很敏感的微服务来说,集中状态校验就会显得很笨重。
2、V2.6版本:JWT+Gateway
在业界实践上,很多企业都在采用基于JWT令牌的无状态安全认证架构,MyShop技术团队也探索出了v2.6版本,即基于JWT+Gateway的模式:
MyShop v2.6:JWT+Gateway
v2.6在v2.5的基础之上发展而来,主要区别如下:
(1)第二步中,v2.5使用的是透明应用令牌,而v2.6使用的是JWT令牌,JWT令牌是自包含数据和签名的;
(2)第四步和第五步,v2.5需要网关每次请求都去AuthService进行校验,而v2.6网关处则不用;此处,网关就可以自行进行令牌的解析和合法性校验;解析完成后,网关就可以得到用户标识信息并向后端微服务传递了;
画外音:这里的JWT令牌有点类似于单块架构阶段v1.x版本下的无状态Session,并且针对于微服务场景进行了扩展应用。
JWT+Gateway的这种做法简化了安全认证架构,降低了AuthService的压力,总体上来说是一种高性能和可扩展的架构,适用于大部分对安全要求不太敏感的微服务应用场景。为什么说是大部分对安全要求不敏感的场景呢,这就需要我们了解一下JWT的原理了。
3、JWT的原理
JWT全程JSON Web Token,是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范,经常用在跨域身份验证。JWT最主要的特点就是它定义了一种紧凑和自包含的JSON对象格式,通过它可以在多个系统或服务之间安全的传递信息,信息经过数字签名可以校验且是可信任的。JWT主要用于 认证授权 和 信息交换 的场景,其令牌结构主要有如下图所示的3个部分组成,且不同部分之间用了一个 . 分隔:
JWT的三个组成部分
这三个组成部分分别是:Header头部、Payload消息体 以及 Signature签名。
画外音:JWT将消息体分为了Header和Payload,其实有点类似于HTTP协议,它也将请求体分为了Header和Body,Header里面一般放元数据,类似于信封上的寄件人和地址等,而Body里面则存放的是实际数据,类似于信封里面的主体内容。
下面我们来看一个JWT令牌的示例:
JWT令牌示例
我们先关注上图中的左半部分,可以看到,三个部分分别使用了不同的颜色标注了出来,并且通过 . 分隔开。
画外音:如果你没看到,那就仔细看一下!如果还没看到,那就打开放大看一下!
一般来说,我们都会通过一些工具例如jwt.io网站来查看JWT解码后的内容,也就是上图中的右半部分就是在jwt.io上进行解码后的内容。
可以看到,Header部分解码后的内容说明了这个令牌的类型是JWT,即JSON Web Token,它使用的算法是HS256算法。
然后,Payload部分解码后的内容说明了这个令牌的颁发者是谁(iss),颁发的时间(iat),令牌过期时间(exp)、这个令牌要颁发给谁(aud)以及目标用户是谁(sub)。这些信息也称为Claims,他们是官方定义的Public Claims。我们在实际使用中,也会定义一些Custom Claims,比如用户的角色信息(Role)等。
对于使用ASP.NET Core的开发童鞋,推荐阅读晓晨的这篇文章实践IdentityServer:《IdentityServer4实战-基于角色的权限控制及Claim详解》。
最后,Signature部分则表明了整个JWT的验证方式是什么样子的,即一个签名公式。如果是采用HS256算法的话,就是下面这个公式:
base64Url(Header)+"."+base64Url(Payload)+"."+base64Url(Signature)
在jwt.io上,我们可以通过输入secret进行一个令牌的合法性的校验,如果不通过则会显示Invalid Signature,通过则显示Signature Verified.
JWT令牌校验
看完了JWT的三个组成部分,及其组装公式,我们可能会发现:Header和Payload的内容是公开可见的,只要拿到Token就可以去类似jwt.io这种工具网站上进行Debug解析,那JWT真的安全吗?
画外音:JWT保证的安全可能是相对的,它不会保证传输信息的保密性,但它会保证信息的可依赖和不可篡改性(通过Signature实现)。因为Secret是保密的,所以即使一般用户拿到了你的token和算法,也无法篡改里面的数据(一旦篡改校验就会不通过)。换句话说,JWT令牌有点类似于现实世界中的签名支票,如下图所示。
签名支票
4、JWT的实现方式
上面我们了解了JWT的原理,现在来看看JWT都是如何来实现的。目前,JWT主要有两种算法实现,一种是HMAC,另一种是RSA。
HMAC流程
首先,来看看HMAC的实现流程:
HAMC流程
Step1.客户端向AuthServer发出登录请求;
Step2.AuthServer校验用户身份,通过后生成JWT数据结构且采用某种HMAC算法+Secret对JWT进行签名,最后将这个签名后的JWT令牌返回给客户端;
Step3.客户端收到JWT后一般都会做本地存储,然后在调用微服务的时候都会带上JWT令牌;
Step4.微服务接收到客户端请求和JWT令牌时,会采用同样的Secret对JWT令牌进行解析和校验,通过后则返回处理后的数据,不通过则一般返回401。
由此看来,HMAC流程中,最重要的就是secret的保密,如果泄露,则整个流程不再安全。
RSA流程
然后,来看看RSA的实现流程:
RSA流程
RSA流程总体上来说和HMAC类似,不同的是AuthServer在颁发JWT令牌的时候采用私钥Private Key进行签名,而微服务端ResourceServer在解析和校验JWT令牌的时候采用公钥Public Key进行签名。
可以看出,RSA流程比HMAC流程总体来说要安全一点,因为只有AuthServer一个地方需要保存私钥,私钥的泄露概率就小很度。其他的微服务端都使用公钥进行解签校验,但是不能够篡改加签。
画外音:其实就类似于对称加密 和 非对称加密的方式。
5、JWT学习小结
本文通过MyShop v2.5存在的问题引出了使用JWT令牌的v2.6架构,并介绍了JWT的概念、原理 和 实现方式。最后,我们来总结一下JWT的优势及不足:
我们重点关注一下JWT的不足:
(1)无状态和吊销无法两全,如果某个用户令牌异常(比如有黑客在干坏事),我们想要吊销这个用户的令牌,但是却没有办法在AuthService上进行统一吊销,一般需要等到这个JWT令牌自然过期才能吊销。又假设我们在AuthService上对某个用户的信息进行了更新,那么相关的Claims信息也必须要等到这个老的JWT过期后重新登录或刷新后产生了新的JWT后才能更新。
(2)JWT的大小会随着Claims的数量增多,也会导致JWT的大小会变大,从而也会导致传输的开销增大。最后,我们可以对有状态的透明令牌和无状态的JWT令牌做一个小结:两者各有适用场景,JWT令牌更适合于安全不敏感场景,透明令牌更适合于安全敏感场景。这里的敏感主要是指和钱、交易、支付相关的场景,以及金融、银行等业务之类的场景,这些场景可以采用集中式的有状态的透明令牌认证,其他的一般性的微服务应用场景则可以使用JWT。
画外音:对中小技术团队来说,特别是技术储备和实力都没那么强的团队,和钱相关的业务场景,宁慢三秒,不抢一秒,因为有时候想快那么一秒,可能就翻车了。
参考资料
杨波,《Spring Boot与K8s云原生应用开发》(极客时间课程,推荐学习)
杨波,《微服务架构160讲》(极客时间课程,推荐学习)