🌝目的
讲述需求
个人项目涉及登录操作,那么我选择jwt作为校验规则。
优点和对比这种“废话”就放在最后说了。
不知道jwt?这里面有我之前面试前总结的一些知识。
✅代码
前言
环境:后端Express、前端任意框架都可
代码记录
安装(没错,它全名叫jsonwebtoken)
npm install jsonwebtoken
引入
const jwt = require('jsonwebtoken');
下发(先说下发再说校验和续期)
let randomCode = 0; // 保存验证码
let codeTimestamp; // 时间戳,这个要在发送验证码或者你的登录操作时候保存
/**
* @param {code} //参数
* @method 校验验证码|下发jwt
* @return {Authorization}
*/
router.post('/veifycode', async (req, res, next) => {
const { code } = req.body
// 比较用户输入的验证码与之前保存的随机验证码
if (code === randomCode) {
const nowTimestamp = Date.now();
const timeDifference = nowTimestamp - codeTimestamp; //时间对比
const validDuration = 300000;//验证码有效期5分钟||你可以根据自己选择设置
if (timeDifference <= validDuration) { //在5分钟内的验证码才有效
const secretKey = uuid; // 秘钥!!
// '重点:上线环境一定要设置成环境变量,泄露的话 你就gg吧~'
// 提供一个生成uuid的网址:https://www.uuid.online/
const payload = { stuId: stuid }; //jwt的'体' 一般是用户的信息,我这里设置学号
const options = { expiresIn: '2h' };// 过期时间
jwt.sign(payload, secretKey, options, (err, token) => {
// 重中之重:jwt的生成固定写法,注意参数写全
if (err) {
console.error('生成 JWT 出错:', err);
return res.status(500).json({ code: 500, message: '生成 JWT 出错' });
}
// 成功生成 JWT,将 JWT 返回给客户端
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Expose-Headers', 'Authorization');
res.setHeader('Authorization', `Bearer ${token}`);
// 我一般设置格式是 Authorization里bearer后边跟token,后边演示取的操作
return res.status(200).json({ message: '验证码输入正确!' });//这时候已经下发成功了
});
} else {
// 验证码已过期,返回错误信息给前端
return res.status(401).send({ code: 401, message: '验证码已过期!' });
}
} else {
return res.status(401).send({ code: 401, message: '验证码错误!' });
}
})
注意:
框框中这一行如果不写,你的生产环境的请求里没有authorization,也就是headers里没有这个信息,jwt校验自然崩了。我排查了很久,查到是要写这个(作为前端,我也不知道原因。。。)
详细的可以看这篇文章
校验jwt和续期
因为如果用户一直有操作的情况下:
1、我们还要秉承之前的两个小时过期吗?--那用户岂不是玩着玩着突然过期了?
2、所以我们要进行 续期
我前端一般会在路由守卫里进行/verify/jwt的请求,每次请求查看jwt是否合格和过期
/**
* @param {headers.authorization}
* @method 校验jwt|续期
*/
router.get('/verify/jwt', async (req, res, next) => {
const token = req.headers.authorization;// 从请求头中取出
if (!token) {
return res.status(401).json({ message: 'No JWT token provided.' });
}
try {
// 取出的格式为`Bearer ${****}`,就是刚才我说的设置风格
//jwt.verify() 是jwt的校验固定写法,传入token信息和秘钥
const decoded = jwt.verify(token.replace('Bearer ', ''), uuid);
const isExpired = Date.now() >= decoded.exp * 1000;
if (isExpired) {
return res.status(401).json({ message: 'JWT令牌已过期' });
}
// 设置新的jwt发给前端
const newToken = jwt.sign({ stuId: stuid }, uuid, { expiresIn: '2h' });
res.setHeader('Authorization', `Bearer ${newToken}`);
res.status(200).json({ message: 'JWT token is valid.' });
} catch (err) {
// 篡改或者其他操作,jwt校验失败
return res.status(401).json({ message: '无效的JWT令牌' });
}
});
前端的操作比较简单
你只需把1、请求头取出来 2、存进内存 3、每次请求带上.....
像极了大象塞进冰箱...
所以这就是网喷的前端狗都会?😅(我不信~)
好了不说了,两个拦截器演示:
http.instance.interceptors.request.use(config => {
// 内存中拿出来,带上
const jwt = localStorage.getItem('jwt')
if (jwt) {
config.headers!.Authorization = `Bearer ${jwt}`
}
*.....其他代码就省略了*
return config
})
http.instance.interceptors.response.use((response) => {
// 取出来、存进去
const jwt = (response.headers.authorization as string)?.split(' ')[1]
jwt && localStorage.setItem('jwt', jwt)
*.....其他代码就省略了*
return response
}, (error: AxiosError) => {
throw error
})
刚才说的路由拦截器(每次跳转前发请求校验jwt):
router.beforeEach(async (to, from) => {
if (to.path === '/' || to.path === '/student/detail' || to.path.startsWith('/welcome') || to.path.startsWith('/login')) {
return true //白名单不校验
} else {
try {
const info = localStorage.getItem('info')
if (!info) {
return '/login?return_to=' + from.path
}
await http.get('/user/verify/jwt') //重点操作在这里~
return true
} catch (error) {
return '/login?return_to=' + from.path
}
}
})
▶️严格模式
前端做了每次请求前的校验,那后端又不相信前端,所以......
1、中间件:每个请求加多写几个字 ❌(这谁能容忍。。)
2、全局校验:请看代码 ✅(这个还可以)
// 白名单
const excludesPath = ['/api/work/mywork', '/api/user/addclassId', '/api/work']
app.use((req, res, next) => {
// 按自己具体需要设置白名单吧! 除了白名单以内所有都要校验
if (rexcludesPath.some(path => path === req._parsedOriginalUrl.pathname)) {
next()
return
}
const token = req.header('Authorization')?.replace('Bearer ', '');
const secretKey = uuid;
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
// jwt不合格的就返回指定参数和信息
res.status(401).json({ message: '你没有权限!' })
} else {
next();
}
});
})
🎦结语
有无描述
JWT 的优点:
- 自包含性: JWT 包含了所需的所有信息,因此无需查找数据库或服务来验证用户。这使得它可以轻松地在各个服务之间传递和共享。
- 可扩展性: 你可以将任何信息添加到 JWT 的负载中,使其适应不同的使用场景。这使得 JWT 非常灵活。
- 跨平台和语言支持: JWT 在不同编程语言和平台之间都是可互操作的。这意味着你可以在前端、后端和移动应用程序中使用相同的 JWT。
- 无状态和分布式: 由于 JWT 是自包含的,服务器不需要在会话中存储任何状态。这在构建无状态和分布式系统时非常有用。
- 签名和加密: JWT 可以被签名来确保其内容未被篡改,并且可以被加密以保护敏感信息。这使得 JWT 在安全性方面具备一定的保障。
- 减少数据库查询: 由于 JWT 包含了一些基本信息,如用户的 ID 和角色,你不必在每个请求中查询数据库来获取用户信息。
JWT 的缺点:
- 潜在的安全性问题: 如果密钥丢失或被泄露,攻击者可能会伪造 JWT 或解密其内容。因此,密钥管理非常重要。
- Token 大小: JWT 的载荷可能会包含大量的信息,导致 Token 变得较大。这可能在网络传输中产生额外的开销。
- 不适合存储敏感信息: 尽管可以加密 JWT,但最好避免在其中存储敏感信息,因为它们可以在客户端被解码。
- 无法即时失效: 一旦 JWT 发出,除非在有效期内过期,否则无法立即使其失效。这可能会在某些情况下导致问题。
- 不支持注销: 一旦 JWT 发出,服务器无法立即使其失效。注销一个 JWT 需要在客户端存储黑名单或增加其他机制。
这个格式肯定是AI写的啦~ 慢慢食用各位大哥(写错轻喷哈,我是弱实习生)
有用,点个赞再走吧? 👀