“The most amazing achievement of the computer software industry is its continuing cancellation of the steady and staggering gains made by the computer hardware industry.” —Henry Petroski
前言
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是所有数据都保存在客户端,每次请求都发回服务器,随后服务器直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
这篇文章中主要讲解了关于JSON Web Tokens(JWT)的基础概念,以及解释JWT为什么会被广泛应用。JWT是确保应用程序信任和安全的重要部分,它允许以诸如用户数据之类的安全的方式声明表示。
为了JWT的工作原理,我们先来看一看关于它的抽象定义:
JSON Web Token(JWT)是一种JSON对象,在RFC 7519中定义为表示双方之间的一组信息的安全方式;它由头部、有效负载和签名三部分组成。
我们完全可以把JWT简单地看成仅仅是符合以下格式的字符串
header.payload.signature
Notes: 合法的JSON必须是使用双引号的字符串
为了理解JWT是如何被使用的,我们将使用User(用户)、Application Server(应用服务器)、Authentication Server(授权服务器)3个简单的实体加以解释说明。其中Authentication Server将提供JWT给用户,用户拿到JWT后就可以和应用(app/website)安全地通信。
上图中,User携带用户名/密码等可证明身份的内容去授权服务器获取JWT信息,每次服务都携带该Token内容与应用服务器进行交互,由应用服务器来验证Token是否是授权系统发放的有效Token,来验证当前业务是否请求是否合法。
接下来我们深入理解JWT是如何构造以及如何验证的。
第一步 创建头部(header)
JWT的头部包含了JWT自身的签名算法信息,它是一个具有如下格式的JSON对象:
1 | { |
这个JSON中, typ属性表示令牌的类型,JWT令牌统一写为JWT。alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256)
第二部 创建有效载荷(payload)
JWT的有效载荷即是JWT的主体内容部分,包含了需要传递的数据(这些数据也被称为JWT的声明)。在我们的🌰中,JWT中存储了用户ID信息。
1 | { |
上面的🌰中,我们的JWT仅仅存储了一个信息。然而JWT 规定了7个官方字段,供选用
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
当然除了官方字段,你还可以在这个部分定义私有字段,诸如:
1 | { |
值得注意的是有效载荷的存储数据的大小会影响整个JWT的大小,一般来说对JWT的大小没有限制但是过大的JWT可能会影响性能或造成潜在的问题。
第三步 创建签名(signature)
签名是为了防止数据被篡改。
JWT的签名使用以下伪代码实现:
1 | // signature algorithm |
该算法所做的使用base64urlEncode对在第一步创建的头部和第二部中的有效负载分别进行编码并用”.”号拼接成字符串。随后使用JWT头中指定的散列算法对字符串使用密钥进行散列。最后再使用base64urlEncode对生成的散列数据进行编码以产生JWT签名。(密钥只有服务器才知道,不能泄露给用户)
这篇文章中,使用base64urlEncode对header和payload进行编码后得到如下结果:
1 | // header |
然后将结果代入上面的伪代码会得到如下签名:
1 | // signature |
第四步 生成JWT
现在我们已经创建完成了JWT的header、payload、signature3个部分,那么再按照
header.payload.signature
的格式将3个部分组合起来就是所谓的JWT了。如下就是我们🌰中的JWT
1 | // JWT |
在JWT官方网站,你可以尝试创建自己的JWT。
回到我们的案例,这个Authentication Server 已经创建了一个JWT,并且发给了用户。
JWT是怎么保护数据的
注意,这里经常会有一个误区,JWT本身和安全没关系,它就仅仅只是一个字符串,使用它来做安全远不如类似于RSA2这样的非对称加密的形式来的实在,由于客户端的程序对用户几乎完全透明,验签的过程对于他们来讲也是透明的,所以安全性肯定不会靠这个来实现,如果实在怕JWT的被盗取,可以考虑在Payload部分加入一些客户端独有的非敏感信息,用于在服务端来进行核验,比如使用MAC-Message Authentication Code、或者公钥之类的等等; 或者干脆就把生效时间设置的短一些,也可以减少暴露的风险。
JWT的数据被编码和被签名,但是没有被加密。编码的目的是转化数据结构,签名是为了数据的接收者验证数据的权威性。所以编码和签名并不会保护数据的安全性。而加密的主要目的才是为了保护数据安全防止未授权访问。对于详细描述编码与加密的区别,可以参考这篇文章。
Since JWT are signed and encoded only, and since JWT are not encrypted, JWT do not guarantee any security for sensitive data.
由于JWT仅仅被编码和签名而没有被加密过的,所以它无法保证任何敏感信息的安全性。
第五步 验证JWT
第四步中我们已经知道JWT的签名生成需要用密钥(secret),这个密钥只有授权服务器知道。应与服务器开启认证处理时,需要从授权服务器获取此密钥。此后当用户携带JWT访问应用服务器时,应用服务器拿到JWT后可以
通过类似第三步的方法生成一个签名,然后与用户携带的JWT的签名进行对比从而验证用户JWT是否有效。
结束语
本文中描述的JWT认证设置使用对称密钥算法(HS256)。您也可以使用非对称算法(例如RS256),即授权服务器具有密钥,并且应用程序服务器具有公钥。了解使用对称和非对称算法之间差异的详细分类。
还应该注意,JWT应该通过HTTPS连接(而不是HTTP)发送。HTTPS有助于防止未经授权的用户窃取所发送的JWT,从而无法拦截服务器和用户之间的通信。
同时,JWT应该设置有效期,并且有效期不要太长。