以前一直都是在postman中测试验证token,现在用在真正的项目中,记录一下在vue项目中token的存储和使用
app.js中需要设置token的地方
const expressJWT = require('express-jwt')
const app = express();
const config = require('./config')
app.use(expressJWT({secret: config.jwtSecretKey}).unless({path: [/^\/api\/users/]}))
const testRouter = require('./routes/testToken')
app.use('/my', testRouter)
config中就是一个秘钥,我单独写了一个文件,不再放上来了
unless后面是不需要token的地址,也就是以/api/users开头的链接都不需要token验证
用到的testToken.js如下:
const express = require('express');
const router = express.Router();
const test = require('../router_handler/testToken')
router.get('/current', test.test)
module.exports = router
使用模块化的写法,真正的路由处理函数写在/router_handler/testToken.js中,内容如下:
const db = require('../db/index');
exports.test = (req, res) => {
const sql = 'select * from users where id = ?'
db.query(sql, req.user.id, (err, results) => {
if(err) return res.send({status: 404, message: err})
if(results.length !== 1) return res.send({status: 404, messsage: '获取用户信息失败,需重新登录'})
res.json(results[0])
})
// res.send(req)
}
实际上就是一个获取用户信息的处理函数,在mysql中根据id查询用户信息
注意,重点来了,经过上述一系列设置后,当我们访问非/api/users开头的链接的时候,都需要验证,也就是在请求参数中提供token,而这个token则是在登录的时候给出的,我写在用户登录的处理函数中user.js,如下
// 用户登录的处理函数
exports.login = (req, res) => {
const userinfo = req.body
const sql = 'select * from users where name = ?'
db.query(sql, userinfo.name, (err, results) => {
if(err) return res.send({status: 404, message: err.message})
if(results.length !== 1) return res.status(404).json('用户不存在!')
const compareResults = bcrypt.compareSync(userinfo.password, results[0].password)
if(!compareResults) return res.status(404).json('密码错误!')
const user = {
id: results[0].id,
name: results[0].name,
avatar: results[0].avatar,
identify: results[0].identify
}
const tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: '10h'})
res.json({
status:200,
message: '登录成功',
// result: results[0],
token: 'Bearer ' + tokenStr
})
})
}
不相关的代码我就不写了,注意看到tokenStr这个变量,就是存储的token值,通过res响应出来。
总结一下,后端存储token的流程:
至此,后端的关键要点已经写完了,进入前端token的存储
前端页面在登录的时候,应该把后端响应过来的token存储起来,这样就可以实现路由守护和免登陆的功能
通过ajax请求路由地址,并访问res.data中的token字符串,并存储在浏览器中,步骤和写法是固定的
我写在Login.vue中,如下:
methods: {
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
this.$axios
.post("/users/login", this.loginUser)
.then(res => {
// console.log(res)
//token
const { token } = res.data
localStorage.setItem("eleToken", token)
this.$router.push("/index");
});
} else {
console.log("error submit!!");
return false;
}
});
}
}
const { token } = res.data
通过解构赋值的方式取出响应数据中的token
localStorage.setItem("eleToken", token)
将token存储在浏览器本地
其实在后端代码中写的unless就是路由守卫的意思,不过前端应该也要有这个,不然不用登录就可以访问到网站的内容了,这显然不合理
看看前端怎么写的,写在router.js中
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from './views/Index.vue'
import Register from './views/Register.vue'
import NotFound from './views/404.vue'
import Login from './views/Login.vue'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{ path: '*', name: '/404', component: NotFound },
{ path: '/', redirect: '/index' },
{ path: '/register', name: 'register', component: Register },
{ path: '/index', name: 'index', component: Index },
{ path: '/login', name: 'login', component: Login },
]
})
// 路由守卫
router.beforeEach((to, from, next) => {
const isLogin = localStorage.eleToken ? true: false
if(to.path == '/login' || to.path == '/register'){
next()
}else {
isLogin ? next() : next('/login')
}
})
export default router
看路由守卫后面的代码就可以了
使用的是router中的beforeEach函数
使用的逻辑如下:
to.path == '/login' || to.path == '/register'
也就是登录和注册不需要拦截,直接放行,用next()还有一点没有搞定,需要判断当前的token是不是过期了的,因为后端写token的时候是给了个10小时的有效期,过期后,应该重新登录才对
这里我写在一个单独的http.js文件中,先上代码
import axios from "axios";
import { Message, Loading } from 'element-ui';
import router from "./router"
let loading;
function startLoading(){
loading = Loading.service({
lock: true,
text: '数据加载中',
background: 'rgba(0,0,0,0.7)'
})
}
function endLoading(){
loading.close();
}
// 请求拦截
axios.interceptors.request.use(config => {
startLoading();
if(localStorage.eleToken){
// 设置统一的请求头
config.headers.Authorization = localStorage.eleToken
}
return config
}, error => {
return Promise.reject(error)
})
//响应拦截
axios.interceptors.response.use(response => {
endLoading();
return response
}, error => {
// 错误提醒
endLoading();
Message.error(error.response.data)
// 获取错误状态码
const {status} = error.response
if(status == 401){
Message.error("token失效,请重新登录")
localStorage.removeItem('eleToken')
router.push('/login')
}
return Promise.reject(error)
})
export default axios
看后面的请求拦截和响应拦截就可以了
分两步:
注意,这里坑的一笔,坑了博主一个下午,我写的
页面会响应一个401的错误状态码
对吗,但是经过测试,token过期后根本就没有跳转到登录页面啊,后来我打印了一下这个status,发现这个值等于0啊,坑爹呢不是吗,所以响应拦截这里正确的写法应该是:
//响应拦截
axios.interceptors.response.use(response => {
endLoading();
return response
}, error => {
// 错误提醒
endLoading();
Message.error(error.response.data)
console.log(error.response)
// 获取错误状态码
const {status} = error.response
console.log(status)
if(status === 0){
Message.error("token失效,请重新登录")
localStorage.removeItem('eleToken')
// console.log(error)
router.push('/login')
}
return Promise.reject(error)
})
把状态码改过来后就正常跳转了。
至此,前后端token的使用都已经顺畅了~~