目录
一. JWT实现Token机制
1. header
2. payload
3. signature
二. 非对称加密,生成私钥和公钥
三. Koa中通过JWT非对称加密生成Token
JWT生成的Token由三部分组成
alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行
加密和解密;
typ:JWT,固定值;
会通过base64Url算法进行编码。
携带的数据,比如我们可以将用户的id和name放到payload中;
默认也会携带iat(issued at),令牌的签发时间;
我们也可以设置过期时间:exp(expiration time);
会通过base64Url算法进行编码。
设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法;
HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token,
也可以解密token;
前面我们提到过,HS256加密算法单一密钥暴露就是非常危险的事情,这个时候我们可以使用非对称加密——RS256;
私钥(private key):用于发布令牌;
公钥(public key):用于验证令牌;
通过cmd以管理员身份运行,创建文件夹keys,cd到对应文件夹下,输入openssl,
genrsa -out private.key 1024
生成私钥保存在对应文件夹
rsa -in private.key -pubout -out public.key
生成公钥保存在对应文件夹
src/controller/user.controller.js
// src/controller/user.controller.js
const jwt = require("jsonwebtoken")
const {
PRIVATE_KEY
} = require("../app/config")
class UserController {
async login(ctx, next) {
const userInfo = ctx.userInfo
const {
customer_number,
customer_phone_number
} = userInfo
// 生成签名
const token = jwt.sign({
customer_number,
customer_phone_number
}, PRIVATE_KEY, {
expiresIn: 60 * 60 * 24, // 过期时间(单位:s秒)
algorithm: 'RS256' // 指定算法
})
ctx.body = {
token: `Bearer ${token}`,
userInfo
}
}
test(ctx) {
ctx.body = "test"
}
}
module.exports = new UserController()
重点关注login方法即可~
ctx.userInfo是我在上一个中间件verifyLoginInfo中保存的用户信息,从用户信息中我取出了其中的customer_number用户编号与customer_phone_number用户手机号作为上面提到JWT中的payload;
PRIVATE_KEY是通过上述方法在本地生成的私钥;
expiresIn是过期时间;
algorithm是选择的加密算法;
生成token后保存在ctx.body中传出
以上完成了派发令牌的过程。
PRIVATE_KEY和PUBLIC_KEY获取的过程如下:
src/app/config.js
// src/app/config.js
const dotenv = require("dotenv")
const fs = require("fs")
const path = require("path")
dotenv.config()
const PRIVATE_KEY = fs.readFileSync(path.resolve(__dirname, './keys/private.key'));
const PUBLIC_KEY = fs.readFileSync(path.resolve(__dirname, './keys/public.key'));
module.exports = {
APP_HOST,
APP_PORT,
MYSQL_HOST,
MYSQL_PORT,
MYSQL_DATABASE,
MYSQL_USER,
MYSQL_PASSWORD
} = process.env
module.exports.PRIVATE_KEY = PRIVATE_KEY
module.exports.PUBLIC_KEY = PUBLIC_KEY
src/middleware/user.middleware.js
// src/middleware/user.middleware.js
const errorType = require("../constants/error-types")
const userService = require("../service/user.service")
const jwt = require("jsonwebtoken")
const { PUBLIC_KEY } = require('../app/config')
const verifyLoginInfo = async(ctx, next) => {
const loginInfo = ctx.request.body
const result = await userService.login(loginInfo)
console.log("verifyLoginInfo", result);
if (result.length === 0) {
const error = new Error(errorType.PHONE_OR_PASSWORD_IS_INCORRECT)
return ctx.app.emit("error", error, ctx)
}
ctx.userInfo = result[0]
next()
}
const verifyAuth = async (ctx, next) => {
console.log("验证授权的middleware~");
const authorization = ctx.header.authorization
if (!authorization) {
const error = new Error(errorType.NO_AUTHORIZATION)
return ctx.app.emit("error", error, ctx)
}
const token = authorization.replace('Bearer ', '');
// 倘若jwt的验证失败会自动抛出错误,因此需要通过try-catch包裹
try {
// 倘若jwt的验证成功,返回值为用户传入的payload+令牌派发时间+令牌过期时间
const result = jwt.verify(token, PUBLIC_KEY, {
algorithms: ["RS256"]
})
console.log("JWT payload:", result);
await next();
} catch(err) {
const error = new Error(errorType.AUTHORIZATION_EXPIRED_OR_INCORRECT);
ctx.app.emit('error', error, ctx);
}
}
module.exports = {
verifyLoginInfo,
verifyAuth
}
重点关注verifyAuth函数即可~
从ctx.header.authorization中获取Authorization授权,再去除开头的"Bearer "即得到token;
传入token,PUBLIC_KEY,对应的加密算法名称进行jwt的验证;
需要注意的是:倘若jwt的验证失败会自动抛出错误,因此需要通过try-catch包裹;
倘若jwt的验证成功,返回值为用户传入的payload+令牌派发时间+令牌过期时间(以秒s为单位的时间戳);
以上完成了验证令牌的过程。
Postman测试
派发令牌
验证令牌
参考资料:深入Node.js技术栈-学习视频教程-腾讯课堂