Appearance
JWT(JSON Web Token)
目前最流行的跨域身份验证解决方案。JWT 解决了 session 不支持分布式架构,无法支持横向扩展的问题,服务器不保存任何会话数据,只需要保存密钥即可。
JWT 组成
header
固定的内容,alg 表示算法,typ 表示类型。
javascript{ alg: 'HS256', typ: 'JWT' }payload
iss 签发人
exp 过期时间
sub 主题
aud 受众
nbf 生效时间
iat 签发时间
jti 编号
Signature
使用方式
请求头,Bearer 表示 token 类型,是一个约定,可以省略。
Authorization: Bearer
<token>url
有些场合 token 可能会放到 url 中,base64 中 + / = 这三个符号在 url 中有特殊含义,+ 替换成 -,/ 替换成 _,结尾的 = 被省略,这就是 base64URL 编码,所以解决办法就是对 payload 进行编码时使用 base64URL,而不是使用 base64,如果你使用 base64,需要替换所有+ -> -,/ -> _,移除结尾的=,解码时先把-替换回+,_替换回/,根据需要补全结尾的=,使长度为4的倍数,再用标准base64解码
如果是 POST 请求也可以放到请求体中。
流程
如果登录成功,基于 JWT 的算法创建一个 Token 并返回给客户端,客户端通过 localStorage 存储。
之后的请求头中把 Token 带上,服务器根据 header 和 payload 重新签名判断是否被 修改,如果没有被修改再解析 token 得到 payload,和 payload 中的 expirationTime 对比 判断是否过期,过期让用户重新登录或者再给一个新的 token。
实现
js
const { createHmac, timingSafeEqual } = require('crypto')
const toBase64URL = s => Buffer.from(JSON.stringify(s)).toString('base64url')
const sign = (base64URL, secret) =>
createHmac('sha256', secret).update(base64URL).digest('base64url')
const jwtEncoder = (payload, secret, header = { typ: 'JWT', alg: 'HS256' }) => {
const base64URL = `${toBase64URL(header)}.${toBase64URL(payload)}`
const signature = sign(base64URL, secret)
return `${base64URL}.${signature}`
}
const jwtDecoder = (token, secret) => {
const segments = token.split('.')
if (segments.length !== 3) {
return null
}
const [header, payload, signature] = segments
const _signature = sign(`${header}.${payload}`, secret)
const signBuffer = Buffer.from(signature)
const _signBuffer = Buffer.from(_signature)
if (
signBuffer.length === _signBuffer.length &&
// 避免 timing attack
timingSafeEqual(signBuffer, _signBuffer)
) {
const _payload = JSON.parse(Buffer.from(payload, 'base64url').toString())
return _payload.exp > Date.now() ? _payload : null
}
return null
}
module.exports = {
jwtEncoder,
jwtDecoder
}js
// A month
const time = 1000 * 60 * 60 * 24 * 30
const secret = 'yourSecret'
const token = jwtEncoder(
{ foo: 'foo', bar: 'bar', exp: Date.now() + time },
secret
)
console.log(token)
const res = jwtDecoder(token, secret)
console.log(res)