# 打开 openssl
> openssl
# 生成私钥:
OpenSSL > genrsa -out rsa_private_key.pem 2048
# 根据私钥生成公钥:
OpenSSL > rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
jsonwebtoken 文档
utils/token.js
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')
// 创建Token
const signToken = (user) => {
// 私钥
const privateKey = fs.readFileSync(path.join(__dirname, '../secretKeys/rsa_private_key.pem'))
const params = {
...user,
_id: user._id,
}
// 将登陆的用户信息生成token,使用私钥进行非对称加密,过期时间为 2h
return jwt.sign(params, privateKey, { algorithm: 'RS256', expiresIn: '2h' })
}
// 校验Token
const verifyToken = (token) => {
// 公钥
const publicKey = fs.readFileSync(path.join(__dirname, '../secretKeys/rsa_public_key.pem'))
try {
// 根据公钥验证token
// 验证成功则返回注册token的原始信息
return jwt.verify(token, publicKey)
} catch (err) {
// 验证失败则返回err
// 处理非法token、过期token等问题
return null
}
}
module.exports = {
signToken,
verifyToken,
}
control/account.js
const usersModel = require('../model/usersManage')
const { getComparePwd } = require('../utils/getBcryptPwd')
const { signToken } = require('../utils/token')
// 用户登录
const login = async (req, res, next) => {
res.set('Content-Type', 'application/json; charset=utf-8')
const { username, password } = req.body
const user = await usersModel.findOneUser(username)
if (user) {
// 验证密码是否正确
const match = await getComparePwd(password, user.password)
if (match) {
// 生成token,并将token返回给前端
const token = signToken(user)
// res.set('X-Access-Token', token)
res.render('success', {
data: JSON.stringify({ token })
})
} else {
res.render('error', {
data: JSON.stringify("用户名或密码错误")
})
}
} else {
res.render('error', {
data: JSON.stringify("用户名或密码错误")
})
}
}
module.exports = {
login,
}
middlewares/auth.js
const { verifyToken } = require('../utils/token')
const auth = (req, res, next) => {
const verifyResult = verifyToken(req.get('x-token'))
if (verifyResult) {
// 将从token中获取的用户信息通过res.locals传给下一个中间件
res.locals.currentUser = verifyResult;
next()
} else {
res.render('notLogin', {
data: JSON.stringify("请登录")
})
}
}
exports.auth = auth
routes/account.js
const express = require('express');
const router = express.Router();
const { login, info } = require('../control/accounts')
const { auth } = require('../middlewares/auth')
// 登录
router.post('/login', login);
// 用户信息
router.get('/info', auth, info);
module.exports = router;
JWT
的最大缺点是,由于服务器不保存 session
状态,因此无法在使用过程中废止某个 token
,或者更改 token
的权限。也就是说,一旦 JWT
签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。因此我们有以下几种方案来优化解决 JWT
无状态的问题。
凡是退出登录的token
都放入黑名单中,定期清理。
每次用户请求服务器都校验token
是否在黑名单
访问时从token
中取出版本号和用户id 和 redis
中存储 用户id和版本号 做对比,不一致则不给访问。
用户登出的时候在redis
中把用户版本号加一。
登录时token
附带创建时间。访问时校验redis
存储的过期时间,如果创建时间大于过期时间则不给访问。
在redis
中存储token
副本,用户请求时候校验,如果redis
中不存在该副本则不给通过。
只让前端清理token
,后端不理会。(大多数)
相当于乐观锁,但是是用redis来实现,速度快
还方便踢人下线
比查阅黑名单列表还要快得多
速度相当快
完美的JWT登录的使用方式