-
最近空余时间比较多,于是把毕业设计的服务端优化了下,之前用的session,用户登录信息都存在服务器上占用服务器的资源,改成token的话,用户再多也不怕(虽然根本没用户em),虽然只是换种鉴权方式,但这个过程中也遇到许多问题,下面我会尽量简洁罗列配置token的几个要点,注意我这里使用的技术栈。
前端:vue全家桶(vue、vue-router、vue-cli、vuex、axios、element-ui) UI库的话还有iview、vant 服务端:egg+node
token鉴权前后端都需要修改,先说服务端吧,注意我用的是egg服务端框架哦
-
首先我的服务端需要一个生成token和解密token的第三方包,jwt,这个egg+node搭
建的服务端其实和前端用脚手架搭建项目很相似,都可以用npm install来安装需要的npm包,执行命令npm install egg-jwt,下载完毕可以看看package.json有没有这个依赖包
-
全局配置jwt,而且需要使用中间件来对每个接口进行token鉴权,只有不需要验证token的接口可以直接略过, 比如登录接口和获取验证码之类的接口,首先是config文件夹下的plugin.js文件
然后config文件夹下的config.default.js文件配置自己的token秘钥
-
中间件路由鉴权
服务端那么多接口总不可能在每个接口方法里解密token吧,所以新建js文件jwtVerify.js来过滤每个接口,token无效或过期或没有token直接将错误信息返回给前端,不需要验证token的接口用一个数组来装接口地址,拦截到请求然后判断下地址是否在这个数组中,这里偷懒全部放全部代码了
"use strict";
// 定制白名单,不需要验证token的接口地址
const whiteList = [ "/user/login","/common/svgimg"];
module.exports = (options) => {
return async function (ctx, next) {
//判断接口路径是否在白名单
if (!whiteList.some(item => item == ctx.request.url)) {
//拿到token,这个是自定义请求头,前端如果定义大写Highlight-Token最后还是会变小写
//别相信某些博主说必须用authorization,自定义请求头想怎么命名怎么命名
let token = ctx.request.header["highlight-token"]
if(token){//如果token存在
// let decoded = ctx.app.jwt.decode(token)
// 这个decode就是jwt的解密token的方法,解密成功的话是下面这样一个对象
// {
// iat: 生成token的时间戳,
// exp: token过期的时间戳,
// username: ""//sign方法传入的一些信息,一般用用户名,登录成功后用这个用户名去数据库拿这个用户的个人信息然后返回给前端
// }
// console.log(decoded);
let verify = ctx.app.jwt.verify(token, options.secret, (error, success) => {
// error是token失效或无效时返回的参数,
// 不对这个error.name做处理的话,token过期或无效了后,服务端会报异常
if (error) {
if (error.name == "TokenExpiredError") {//token过期
ctx.body = {
data: {},
code: 50014,
message: "token过期"
};
} else if (error.name == "JsonWebTokenError") {//无效的token
ctx.body = {
data: {},
code: 50009,
message: "token无效"
};
}
}
else {
return success
}
})
if (verify) {
if (verify.iat < verify.exp) {
// console.log(verify);
await next();//通過
}
else {
// console.log(verify);
return
}
}
else {
return
}
}else{
ctx.body = {
data: {},
code: 50008,
message: "未设置token"
};
return;
}
}else{
await next()
}
}
}
- router.js使用jwt中间件,这里看一个登录接口就够了
"use strict";
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller, middleware } = app;
router.post("/user/login", middleware.jwtVerify(app.config.jwt), controller.user.login);
};
然后就可以在登录接口用jwt生成token做验证了
const token = this.app.jwt.sign({ username: obj.username }, this.app.config.jwt.secret, { expiresIn: "24h" });//h、m、s分别对应时分秒
然后登录成功就把这个token返回给前端,前端登录成功之后的每次请求将这个token加到请求头中传给后台就可以在后台做token验证了
- 前端携带token,拦截axios请求处理token无效过期和一些接口异常的情况
拦截axios请求加上token
import axios from "axios"
import { MessageBox, Message } from "element-ui"
import store from "@/store"
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
withCredentials: true, // 跨域请求时发送 cookies
timeout: 5000, // request timeout
})
service.interceptors.request.use(
config => {
// Do something before request is sent
// 保存在store里的token的
if (store.getters.token) {
// 设置的自定义请求头Highlight-Token在后端获取时变为小写highlight-token
// 这里别相信很多博主说的要拼接Bearer加一个空格,像这样
// config.headers["Highlight-Token"] = Bearer+" "+getToken();
// 甚至还在后台写了个 token = request.header["highlight-token"].split[" "][1]
// 我真是服了这群人了
config.headers["Highlight-Token"] = store.getters.token;
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data;
if (res) {
// 这个是后台自定义的code哦,不是http里的状态码哦
if (res.code !== 200) {
// 在这里处理异常,如res.code===50008 token失效重置token等等
}
} else {
return Promise.reject("error");
}
},
error => {
// 接口返回数据异常信息
}
)
export default service
下次再写取消axios请求,因为token失效后后续的请求都没有必要再发