JSON Web Token 是 rfc7519 出的一份标准,使用 JSON 来传递数据,用于断定用户是否登陆状态。javascript
jwt 以前,使用 session 来作用户认证。html
如下代码均使用 javascript 编写。前端
原文连接见 山月的博客java
传统登陆的方式是使用 session + token
。mysql
token
是指在客户端使用 token 做为用户状态凭证,浏览器通常存储在 localStorage
或者 cookie
中。git
session
是指在服务器端使用 redis 或者 sql 类数据库,存储 user_id 以及 token 的键值对关系,基本工做原理以下。github
const sessions = {
"ABCED1": 10086,
"CDEFA0": 10010
}
// 经过 token 获取 user_id, 完成认证过程
function getUserIdByToken (token) {
return sessions[token]
}
复制代码
若是存储在 cookie
中就是常常听到的 session + cookie
的登陆方案。其实存储在 cookie
,localStorage
甚至 IndexedDB
或者 WebSQL
各有利弊,核心思想一致。web
关于 cookie
以及 token
优缺点,在 token authetication vs cookies 中有讨论。redis
若是不使用 cookie,能够采起 localStorage + Authorization
的方式进行认证。算法
// http 的头,每次请求权限接口时,须要携带 Authorization Header
const headers = {
Authorization: `Bearer ${localStorage.get('token')}`
}
复制代码
推荐一个库 localForage,使用
IndexedDB
,WebSQL
以及IndexedDB
作键值对存储。
session
须要在数据库中保持用户及token对应信息,因此叫 有状态。
试想一下,如何在数据库中不保持用户状态也能够登陆。
第一种方法: 前端直接传 user_id 给服务端
缺点也特别特别明显,容易被用户篡改为任务 user_id,权限设置形同虚设。不过思路正确,接着往下走。
改进: 对 user_id 进行对称加密
比上边略微强点,若是说上一种方法是空窗户,这种方法就是糊了纸的窗户。
改进: 对 user_id 不须要加密,只须要进行签名,保证不被篡改
这即是 jwt 的思想,user_id,加密算法和签名一块儿存储到客户端,每次请求接口时,服务器判断签名是否一致。
jwt 由 Header
,Payload
以及 Signature
由 .
拼接而成。
Header 由非对称加密算法和类型组成,以下
const header = {
// 加密算法
alg: 'HS256',
type: 'jwt'
}
复制代码
Payload 中由 Registered Claim 以及须要通讯的数据组成。这些数据字段也叫 Claim
。
Registered Claim
中比较重要的是 "exp" Claim
表示过时时间,在用户登陆时会设置过时时间。
const payload = {
// 表示 jwt 建立时间
iat: 1532135735,
// 表示 jwt 过时时间
exp: 1532136735,
// 用户 id,用以通讯
user_id: 10086
}
复制代码
Sign 由 Header
,Payload
以及 secretOrPrivateKey
计算而成。
对于 secretOrPrivateKey
,若是加密算法采用 HMAC
,则为字符串,若是采用 RSA
或者 ECDSA
,则为 PrivateKey。
// 由 HMACSHA256 算法进行签名,secret 不能外泄
const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret)
// jwt 由三部分拼接而成
const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign
复制代码
从生成 jwt 规则可知客户端能够解析出 payload,所以不要在 payload 中携带敏感数据,好比用户密码
在生成规则中可知,jwt 前两部分是对 header 以及 payload 的 base64 编码。
当服务器收到客户端的 token 后,解析前两部分获得 header 以及 payload,并使用 header 中的算法与 secretOrPrivateKey 进行签名,判断与 jwt 中的签名是否一致。
如何判断 token 过时?
由上可知,jwt 并不对数据进行加密,而是对数据进行签名,保证不被篡改。除了在登陆中能够用到,在进行邮箱校验和图形验证码也能够用到。
在登陆时,输入密码错误次数过多会出现图形验证码。
图形验证码的原理是给客户端一个图形,而且在服务器端保存与这个图片配对的字符串,之前也大都经过 session 来实现。
能够把验证码配对的字符串做为 secret,进行无状态校验。
const jwt = require('jsonwebtoken')
// 假设验证码为字符验证码,字符为 ACDE,10分钟失效
const token = jwt.sign({ userId: 10085 }, secrect + 'ACDE', { expiresIn: 60 * 10 })
复制代码
如今网站在注册成功后会进行邮箱校验,具体作法是给邮箱发一个连接,用户点开连接校验成功。
// 把邮箱以及用户id绑定在一块儿
const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 })
// 在此连接校验验证码
const link = `https://example.com/code=${code}`
复制代码
关于无状态和有状态,在其它技术方向也有对比,好比 React 的 stateLess component 以及 stateful component,函数式编程中的反作用能够理解为状态,http 也是一个无状态协议,须要靠 header 以及 cookie 携带状态。
在用户认证这里,有无状态是指是否依赖外部数据存储,如 mysql,redis 等。
思考如下几个关于登陆的问题如何使用 session 以及 jwt 实现
由于 jwt 无状态,不保存用户设备信息,无法单纯使用它完成以上问题,能够再利用数据库保存一些状态完成。
对于这个需求,session 稍微简单些,毕竟 jwt 也须要依赖数据库。
对于这个需求,jwt 略简单些,而使用 session 还须要多维护一张 token 表。
从以上问题得知,若是不须要控制登陆设备数量以及设备信息,无状态的 jwt 是一个不错的选择。一旦涉及到了设备信息,就须要对 jwt 添加额外的状态支持,增长了认证的复杂度,此时选用 session 是一个不错的选择。
jwt 不是万能的,是否采用 jwt,须要根据业务需求来肯定。