token介绍
Token 的中文意思是"令牌"。主要用来身份验证。比传统的身份验证方法,Token 有扩展性强,安全性高的特点,非常适合用在 Web 应用或者移动应用上。
它是服务端生成的字符串,作为客户端进行请求的一个标识。
token出现的背景
以前,Web 基本上就是文档的浏览而已,每次请求都是一个新的HTTP协议, 就是请求加响应,尤其是我不用记住是谁刚刚发了HTTP请求。
随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,面临一个问题,必须记住哪些人登录系统,也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,起初想出的办法就是给每一个人发一个唯一的sessionid(一个随机的字符串),下次大家再向我发起HTTP请求的时候,把这个sessionid带着, 这样我就能区分开谁是谁了。
每个人只需要保存自己的session id,而服务器要保存所有人的session id !这对服务器说是一个巨大的开销, 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统, 那session id会保存在机器A上, 假设小F的下一次请求被转发到机器B怎么办? 机器B可没有小F的 session id啊。
那只好做session 的复制了, 把session id 在服务器之间搬来搬去, 快累死了。
于是有人就在思考, 我为什么要保存这可恶的session id, 只让每个客户端去保存多好?
可是如果不保存这些session id , 怎么验证客户端发给我的session id 的确是我生成的呢? 如果不去验证,我们都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。所以,关键点就是验证 !
比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来
不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token服务器不保存,当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
解除了session id这个负担, 可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。 这种无状态的感觉实在是太好了。
token对比session
这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:
- 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
- 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
- 更适用CDN:可以通过内容分发网络请求服务端的所有资料
- 更适用于移动端:当客户端是非浏览器平台时,cookie是不支持的,采用token认证方式会简单很多
- 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
JWT介绍
jwt是token的一种实现方式,它将用户信息加密到token
里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token
的正确性,只要正确即通过验证。
优点
- 简洁: 可以通过URL、POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
- 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库
- Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
- 不需要在服务端保存会话信息,特别适用于分布式微服务
缺点
- 无法作废已颁布的令牌
- 不易应对数据过期
示例
组成
JWT是由.
分割的三部分组成
jwt token = base64 URL(头部) + "." +base64 URL(负载) + "." + base64 URL(签名)
Base64 URL是一种编码,不是加密方式,是可以解码的
头部(Header)
{
"alg": "HS256",
"typ": "JWT"
}
-
alg是签名用的算法,默认为HMAC SHA256(写为HS256),JWT默认也是推荐的算法
-
typ属性表示令牌的类型,JWT令牌统一写为JWT
负载(Payload)
JWT的主体内容部分,也是JSON,包含需要传递的数据
JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除默认字段外,我们还可以自定义字段,一般会把包含用户信息的数据放到payload中,如下
{
"sub": "1234567890",
"userID": "123456",
"admin": true
}
JWT里不要存放敏感信息,因为base64 URL编码是可以解码的
签名(Signature)
首先,需要指定一个密钥(secretKey),该密钥只存在服务器中,不能向用户公开公开,使用header中指定的签名算法默认情况下为HMAC SHA256)根据以下公式生成签名。
Signature = HS256(Base64(头部) + "." + Base64(负载) , secretKey)
jwt校验
header 和 payload可以直接利用base64 URL解码出原文。
从header中获取哈希签名的算法,从payload中获取有效数据。
signature使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端用header、payload、secretKey通过header中的签名算法进行再次加密,比对加密后的数据和客户端发送过来token中的的是否一致。
无论改了头部、负载、和签名中的哪个部分,token校验都不会通过