平常前端项目中,我们通常使用 token 来进行身份验证 —— 客户登录成功,接口就会返回一个 token,之后前端每次发送请求就在请求头中添加个值为 token 的 authorization 字段来表明身份。至于 token 是怎么生成的?那一长串的字母数字到底有什么含义?或许一些身为前端的小伙伴们长久以来并不甚了解。那么本文,就来对 token 的组成及生成方法做个基本的介绍吧。
生成 token
在使用 koa 搭建的后端项目中,可以安装 jsonwebtoken 这个库来生成 token:
npm i jsonwebtoken
之后引入 jwt
,通过 jwt.sign()
即可得到 token,传入的第一个参数是 token 携带的信息,第二个参数是用于数字签名的密钥,第三个参数可以传入一些配置,比如过期时间 expiresIn
,单位为秒:
// 代码片段一,省略部分代码
const jwt = require('jsonwebtoken')
loginRouter.get('/login', (ctx, next) => {
const token = jwt.sign({ name: 'Jay' }, 'aaabbbccc', { expiresIn: 60 * 60 })
ctx.body = {
msg: '登录成功',
token
}
})
使用浏览器发送请求,可以看到生成的 token:
token 的组成
我们可以将生成的 token 复制粘贴到 jwt 官网的 debugger 工具里进行解码:
可以发现 token 使用 .
分割成了 3 部分:
1. Header
即 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
这部分,为 Base64 编码后的结果,其中有两个属性:
alg
,也就是 Algorithm(算法) 的缩写,指定加密算法,默认为 HS256(HMAC-SHA256),是一种单项散列函数,加密解密采用同个密钥;typ
,token 的类型(type),通常可以固定写成 JWT。
所以这部分结果,如果保持代码片段一不变,浏览器每次重新请求得到的结果也都不会变化。
2. Payload
token 携带的数据,除了我们在代码片段一中传入的 name
信息以及过期时间 exp
,还有默认携带的 iat
(issued at),为 token 的签发时间。Payload 部分的数据同样是经过 Base64 编码的。
3. Signature
签名部分,通过 HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
得到,即将 Header 和 Payload 的 Base64 编码结果与数字签名的密钥 secret
结合,通过 HS256 算法生成,这部份的结果,浏览器每次重新请求时都会重新生成。
验证 token
假定前端是通过 Bearer Token 类型向服务器传递的 token,实际上就是将 token 赋值给请求头的 authorization
属性。那么后端在验证时,可以通过 ctx.headers.authorization
拿到带有 Bearer
前缀(注意 Bearer 后有个空格)的 token 。之后再将获取的 token 以及代码片段一生成 token 时传入的密钥一起传给 jwt.verify()
,如果验证成功,则 res
可以得到 Payload 数据;如果验证失败,则会报错,可用 try catch
捕获 :
// 代码片段二
userRouter.get('/center', (ctx, next) => {
const authorization = ctx.headers.authorization
const token = authorization.replace('Bearer ', '')
try {
const res = jwt.verify(token, 'aaabbbccc')
console.log('res', res) // res { name: 'Jay', iat: 1679814009, exp: 1679817609 }
ctx.body = '验证成功'
} catch (error) {
ctx.body = '验证失败'
}
})
采用非对称加密算法
在代码片段一中使用 jwt 生成 token 时,默认采用的加密算法是 HS256,安全性不高,所以一般公司里的项目采用的都是非对称加密算法,比如 RSA 算法,使用私钥来颁发 token,使用公钥来验证 token,关于加密算法这部分知识,若想了解更多,可移步 《前端加密》。
生成公私密钥
在 windows 系统下,公私密钥的生成可以去下载安装 OpenSSL, 然后通过系统自带的命令行工具使用 openssl 生成。也可以直接通过 git 的 bash 工具(集成了 OpenSSL)来生成 。首先生成私钥:
进入到要保存密钥的目录下,输入 openssl
按下回车:
接着就可以输入 genrsa -out private.key 1024
来生成 rsa 算法的私钥:
生成的私钥如下:
有了私钥,再使用 rsa -in private.key -pubout -out public.key
生成与之对应的公钥:
公钥如下:
修改之前的代码
有了一对公私密钥后,生成 token 时我们就可以将代码片段一稍加修改,将传入的密钥改为私钥,并在配置对象中指定要使用的算法为 RS256
:
const token = jwt.sign({ name: 'Jay' }, privateKey, {
expiresIn: 60 * 60,
algorithm: 'RS256'
})
验证 token 时,也需将代码片段二中用公钥替换原本的密钥,然后添加算法配置,只不过这里 algorithm
的值为数组,因为在验证时是可以通过多种算法依次尝试的:
const res = jwt.verify(token, publicKey, {
algorithm: ['RS256']
})
privateKey
和 publicKey
可以通过 fs 模块获取:
const fs = require('fs')
const privateKey = fs.readFileSync('./src/keys/private.key')
const publicKey = fs.readFileSync('./src/keys/public.key')