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)双 token
双 Token 机制是一种将身份验证与权限管理分离的现代鉴权方案,它通过使用两个不同功能的令牌来提升安全性和用户体验。该机制通常包含两个令牌:1.访问令牌:用于日常请求API,有效期较短(如15分钟至几小时)。2.刷新令牌:用于在访问令牌过期后获取新的访问令牌,有效期较长(如7天至30天)。用户登录成功后,服务端同时生成并返回访问令牌和刷新令牌。用户发送 API 请求时:客户端在请求头中携带访问令牌访问。当访问令牌过期时,客户端使用刷新令牌向服务端请求新的访问令牌(和可选的新的刷新令牌),用户无需重新登录。用户退出登录时时,客户端清除本地访问令牌,同时服务端将对应的刷新令牌从 redis 中删除,如果访问令牌没过期,将访问令牌加入 redis 中的访问令牌黑名单中,并设置和访问令牌相同的剩余过期时间
优点
由于刷新令牌存储在 Redis 中,泄露风险极低,访问令牌中只需要存储用户标识,不再需要存储用户的基本信息,且访问令牌有效期短,泄露后,攻击窗口也很有限。