嗷呜, 预感又是一篇长的水文, 但是内心好激动哦
适用于前端(了解 node express 框架的人看), 想要了解后端的人看
好了, 开始废话模式
前后端 token 的使用
最近在做一个后台管理项目, 但是我一个卑微的前端我去哪里找接口的, 没有, 只好我自己写
哎, 我能有什么坏心思呢, 没有
1. 了解 JWT
**利用
token
进行用户身份验证的流程:**
- 客户端使用用户名和密码请求登录
- 服务端收到请求,验证用户名和密码
- 验证成功后,服务端会签发一个token,再把这个token返回给客户端
- 客户端收到token后可以把它存储起来,比如放到cookie中
- 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
支持跨域访问:*cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
而
JWT
就是上述流程当中
token
的一种具体实现方式,其全称是
JSON Web Token
,官网地址:https://jwt.io/
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个
JWT token
,**并且这个
JWT token
带有签名信息,接收后可以校验是否被篡改**,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT 的验证证流程:
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
- 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
- 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
- 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
- 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
- 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
用案例来了解, 唔, 我把 app.js 拆分成了下面的几个部分, 代码的顺序没有改变, 依次看就可以
/*
* Start at 2022.4.30
* Author: ximingx.
* Github: https://github.com/ximingx
* Csdn: https://ximingx.blog.csdn.net/
*/// 导入 express 模块// 创建 express 的服务器实例const express =require('express')const app =express()const port = process.env.PORT||2022;// 跨域设置const cors =require('cors')
app.use(cors())// req.body 解析
app.use(express.json());
app.use(express.urlencoded({extended:false}));
1.1 安装并导入
首先需要我们去安装依赖
>yarnadd jsonwebtoken express-jwt
然后导入
// 1. 安装并导入 JWT 相关的两个包, express-jwt版本推荐6.1.1, 最近他更新了, 可能会出现一些意外的问题const jwt =require('jsonwebtoken')const expressJWT =require('express-jwt')
1.2 定义你自己的 secret 密钥
// 2. 定义 secret 密钥,建议将密钥命名为 secretKeyconst secretKey ='ximingx';
1.3 JWT字符串解析
// 5. JWT字符串解析// 用于接收到客户端传来的请求, 验证是否含有 token// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 `req.user` 属性上
app.use(expressJWT({secret: secretKey,algorithms:['HS256']}).unless({path:['/login','/register']}))
1.4 登陆后生成 token
// 登录的路由, 在这里登录成功后, 返回 token 字符串
app.post('/login',(req, res)=>{// 哎呀,这里就是一个简单的登录测试, 没搞的很复杂if(!(req.body.username =='admin'&& req.body.password =="root")){return res.json({code:1,msg:'用户名或密码错误'})}// 3. 用户登陆成功后, 需要生成token// 参数1:用户的信息对象// 参数2:加密的秘钥// 参数3:配置对象,可以配置当前 token 的有效期const tokenStr = jwt.sign({username: req.body.username
}, secretKey,{expiresIn:'1h'})// 返回登陆成功后返回数据
res.json({status:200,message:'登录成功!',// 4. 并通过 token 属性发送给客户端token: tokenStr,})})
1.5 验证
// 这里需要这样请求, 否则 token 会无效报错// axios({// method: 'post',// url: 'http://localhost:2022/adimin',// headers: {// 'Authorization': 'Bearer ' + token// }// })// 可以分别用带 token 和不带 token 验证一下// 这是一个有权限的 API 接口
app.get('/admin',function(req, res){// 6. 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)// {// "status": 200,// "message": "获取用户信息成功!",// "data": {// "username": "admin",// "iat": 1651242605, // 创建时间// "exp": 1651246205 // token 有效期// }// }
res.send({status:200,message:'获取用户信息成功!',data: req.user,// 要发送给客户端的用户信息})})
这是不带 token 的请求
当我们在登陆后, 获取 token
再次进行测试, 带上 token 就可以啦
1.6 捕获 token 过期异常
// 7:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next)=>{// 这次错误是由 token 解析失败导致的if(err.name ==='UnauthorizedError'){return res.send({status:401,message:'无效的token',})}
res.send({status:500,message:'未知的错误',})})// 端口号监听, 不重要
app.listen(port,()=>{
console.log(`Server is running at http://localhost:${port}`);});
2. 搭建一个简单的后台服务
于是我用了
node
的
express
框架简单的做了一个服务器
下面是
app.js
文件, 看完上面的 jwt 应该就可以很好地理解了
/*
* Start at 2022.
* Author: ximingx.
* Github: https://github.com/ximingx
* Csdn: https://ximingx.blog.csdn.net/
*/// 导入 express 模块// 创建 express 的服务器实例const express =require('express')const app =express()const port = process.env.PORT||3000;// 跨域设置const cors =require('cors')
app.use(cors())// req.body 解析
app.use(express.json());
app.use(express.urlencoded({extended:false}));// 加载数据库require('./db/index');// 5. JWT字符串解析const expressJWT =require('express-jwt')// 2. 定义 secret 密钥,建议将密钥命名为 secretKeyconst secretKey ='ximingx';// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 `req.user` 属性上
app.use(expressJWT({secret: secretKey,algorithms:['HS256']}).unless({path:['/api/ximingx/v1/login']}));// 自定义模块const{writeLog}=require('./log/log');// 路由
app.use('/api/ximingx/v1',require('./router/index'));// 全局捕获错误
app.use((err, req, res, next)=>{writeLog(err);// 这次错误是由 token 解析失败导致的if(err.name ==='UnauthorizedError'){return res.send({status:401,message:'无效的token',})}return res.status(500).send('未知的错误!');});// 监听端口
app.listen(3000,()=>{
console.log(`server is running at http://localhost:${port}`);});
这是我用于登录验证的路由
// 除了 /api/ximingx/v1/login 其他的路由都需要 token
app.use(expressJWT({secret: secretKey,algorithms:['HS256']}).unless({path:['/api/ximingx/v1/login']}));
app.use('/api/ximingx/v1',require('./router/index'));
router.js
const express =require('express');const router = express.Router();// 1. 安装并导入 JWT 相关的两个包, express-jwt版本推荐6.1.1, 最近他更新了, 可能会出现一些意外的问题const jwt =require('jsonwebtoken')// 2. 定义 secret 密钥,建议将密钥命名为 secretKeyconst secretKey ='ximingx';// 自定义模块const{UserLogin, findUser, createUser}=require('../db/user')const{LoginRegex}=require('../utils/regex')const{writeLog}=require('../log/log')
router.post('/login',(req, res)=>{if(!LoginRegex(req.body)){writeLog("Login 参数不支持")return res.json({title:"register",code:400,data:{info:"ximingx",message:"参数不支持",}})}UserLogin(req.body).then(user=>{if(user ==null){return res.json({title:"login",code:400,data:{info:"ximingx",message:"用户名或者密码错误"}})}// 3. 用户登陆成功后, 需要生成token// 参数1:用户的信息对象// 参数2:加密的秘钥// 参数3:配置对象,可以配置当前 token 的有效期const tokenStr = jwt.sign({username: req.body.username
}, secretKey,{expiresIn:'1h'})
res.json({title:"login",code:200,data:{com:"ximingx",message: req.body,// 4. 并通过 token 属性发送给客户端token: tokenStr,}})}).catch(err=>{writeLog(err)return res.json({title:"login",code:400,dara:{info:"ximingx",message:"用户名或者密码错误",}})})});
3. 前端登录页面
这里我们进行请求
axios({
url: ‘http://localhost:3000/api/ximingx/v1/login’,
method: ‘POST’,
data: {
username: this.username,
password: this.password,
}
})
在登录成功后进行存储
localStorage.setItem(‘token’, res.data.data.token);
inputInfo:function(){let password =/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,18}$/;let username =/^[a-zA-Z0-9]{3,12}$/;if(!(username.test(this.username)&& password.test(this.password))){return ElNotification.error('用户名或者密码不合法!');}axios({url:'http://localhost:3000/api/ximingx/v1/login',method:'POST',data:{username:this.username,password:this.password,}}).then(res=>{if(res.data.code ==200){
localStorage.setItem('token', res.data.data.token);ElNotification({title:'Success',message:'登录成功 请稍等...',type:'success',});this.$router.push({path:'/home',})}else{
ElNotification.error({title:'用户名或者密码错误',message:`请重新输入`,})}}).catch(res=>{ElNotification({title:'Error',message:'用户名或者密码错误',type:'error',});})},
4. 导航守卫
但是呢, 即使是没有 token, 我们在 url 里输入别的页面仍然会跳转
这里就需要我们在前端 router.js 里配置, 判读有无 token, 使别人不能随便的进行别的有权限的页面的访问
// to 从哪一个路径来, from 到哪一个路径去, next 是否继续执行, 表示放行
router.beforeEach((to, from, next)=>{if(to.path ==='/login'){returnnext()}else{const token = localStorage.getItem('token')if(!token){returnnext('/login')}returnnext()}})
但是还是有问题, 别人可以自己加 token 啊
然后我们就可以快乐的跳转了
所以呢, 最后还应该给导航守卫还应该添加验证
版权归原作者 ximingx 所有, 如有侵权,请联系我们删除。