相关环境
服务端:express jsonwebtoken mysql bluebird debug
前端:axios iview js-cookie
思路
前端:路由守卫+vuex+js-cookie+axios(或sessionStorage)
1、路由守卫中首先判断本地cookie里面是否有token。(即每访问一个新的url都会去服务器校验token是否合法)
import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'
import store from '@/store'
import { setTitle, setToken, getToken } from '@/lib/util'
Vue.use(Router)
const router = new Router({
// mode: 'history', // 默认hash
routes
})
// 全局导航守卫
router.beforeEach((to, from, next) => {
to.meta && setTitle(to.meta.titile)
const token = getToken()
if(token){
//通过vuex去服务器校验token
store.dispatch('authorization', token).then(()=>{
if(to.name==='login'){
next({ name: 'home' })
}else{
next()
}
}).catch(()=>{
setToken('')
next({ name: 'login'})
})
}else{
if(to.name === 'login')next()
else next({ name: 'login' })// 如果没有登录则跳转到login
}
})
export default router
util
import Cookies from 'js-cookie'
export const setToken = (token, tokenName = 'token') => {
Cookies.set(tokenName, token)
}
export const getToken = (tokenName = 'token') => {
return Cookies.get(tokenName)
}
store user
import { login, authorization } from '@/api/user'
import { setToken } from '@/lib/util'
const state = {
userName: 'Yafully'
}
const getters = {
firstLetter: (state) => {
return state.userName.substr(0, 1)
}
}
const mutations = {
SET_USER_NAME (state, params) {
state.userName = params
},
SET_RULES (state, rules) {
state.rules = rules
}
}
const actions = {
updateUserName ({ commit, state, rootState, dispatch }) {
// rootState.appName
},
login ({ commit }, { userName, password }) {
return new Promise((resolve, reject) => {
login({ userName, password }).then(res => {
if (res.code === 200 && res.token) {
setToken(res.token)
resolve()
} else {
reject(new Error('错误'))
}
}).catch(error => {
reject(error)
})
})
},
authorization ({ commit }, token) {
return new Promise((resolve, reject) => {
authorization().then(res => {
if (parseInt(res.code) === 401) {
reject(new Error('token error'))
} else {
//服务器校验token完成把新的token更新到cookie
setToken(res.token)
resolve()
//commit('SET_RULES', res.data.rules.component)
}
}).catch(error => {
reject(error)
})
})
},
logout () {
setToken('')
}
}
export default {
getters,
state,
mutations,
actions
}
2、如果有则发送token去服务器校验合法性。
如果合法则继续判断当前路由来路是否为登录页面,如果是则跳转,如果不是则停留;同时把写入新的token到本地cookie续期;
如果没有则继续判断路由来路,如果是登录页则停留,如果不是登录页则跳转登录;
3、登录页面提交登录信息到后端
<template>
<div class="login">
<h2>用户登录</h2>
<Form ref="formInline" :model="formInline" :rules="ruleInline" label-position="top">
<FormItem prop="user" label="用户名:">
<Input type="text" v-model="formInline.user" placeholder="Username">
<Icon type="ios-person-outline" slot="prepend"></Icon>
</Input>
</FormItem>
<FormItem prop="password" label="密码:">
<Input type="password" v-model="formInline.password" placeholder="Password">
<Icon type="ios-lock-outline" slot="prepend"></Icon>
</Input>
</FormItem>
<FormItem class="right">
<Button type="primary" @click="handleSubmit('formInline')">Signin</Button>
</FormItem>
</Form>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
data () {
const username = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please enter your username'));
} else {
if (!/^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$|^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)) {
// 验证用户名是否为手机号或邮箱
callback(new Error('Please enter a valid username.'));
}
callback();
}
}
return {
formInline: {
user: 'yafully@gmail.com',
password: '123456'
},
ruleInline: {
user: [
{ required: true, validator: username, trigger: 'blur' }
],
password: [
{ required: true, message: 'Please fill in the password.', trigger: 'blur' },
{ type: 'string', min: 6, message: 'The password length cannot be less than 6 bits', trigger: 'blur' }
]
}
}
},
methods: {
...mapActions([
'login'
]),
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
//this.$Message.success('Success!');
this.login({
userName: this.formInline.user,
password: this.formInline.password
}).then((res)=>{
this.$router.push({ name: 'home' })
}).catch(error => {
console.log(error)
})
} else {
this.$Message.error('Fail!');
}
})
}
}
}
</script>
<style lang="less">
@import '../assets/less/login.less';
</style>
4、退出登录 清空本地cookie token即可
后端:express jsonwebtoken mysql bluebird
1、路由中间件控制全局访问url,将登录的url设为白名单不去校验token,白名单以外的请求则全部校验token;
const express = require('express');
var bodyParser = require('body-parser');
const jwt = require("jsonwebtoken");
const whitelist = require('./lib/whitelist');//路由白名单
const indexRouter = require('./routes');
const usersRouter = require('./routes/user');
const app = express();
// 解析 application/json
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(bodyParser.json({ type: 'application/vnd.api+json' }))
const hasOneOf = (str, arr)=> {
return arr.some(item => {
return item.includes(str)
})
}
//设置跨域
app.all('*', function(req, res, next) {
// res.header("Access-Control-Allow-Origin", "*");
// res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
// res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
// res.header("Access-Control-Request-Headers:content-type,xfilecategory,xfilename,xfilesize");
// res.header("X-Powered-By",' 3.2.1');
// if(req.method=="OPTIONS") res.sendStatus(200);/*让options请求快速返回*/
let method = req.method.toLowerCase()
let path = req.path
if(whitelist[method] && hasOneOf(path, whitelist[method])){
next()
}else{
const token = req.headers.authorization
if(!token){
res.sendStatus(403);
}else{
jwt.verify(token, 'abcd', (error, decode)=>{
if(error) {
res.send({
code: 401,
message: 'token error',
data:{}
})
}else{
req.userName = decode.name
next()
}
})
}
}
});
app.use('/index', indexRouter);
app.use('/users', usersRouter);
app.use(function(req, res, next){
next(creatError(404));
})
app.listen(3000, function(){
//let u = await getPasswordByName('yafully');
console.log('server start');
});
2、登录,这是个异步操作,首先通过bluebird数据库中间件用前端发送过来的用户名去数据库查找密码跟前端发来的密码对比;对比成功则返回token到前端,否则返回错误;
const express = require('express');
//const path = require('path');
//const app = express();
const jwt = require("jsonwebtoken");
const router = express.Router();
const getPasswordByName = require('../lib/user');
router.post('/getUserInfo', function(req, res, next){
res.status(200).send({
code:200,
data:{
name: 'Yafully'
}
})
});
// 模拟一个登陆的接口
router.post('/login',async function(req, res, next){
// 登录成功获取用户名
const {userName, password} = req.body//req.query
if(userName) {
const userInfo = password ? await getPasswordByName(userName) : '';
//console.log(userInfo);
// console.log(password);
// console.log(userInfo.password);
if(!userInfo || !password || userInfo.password !==password){
res.status(401).send({
code: 401,
message: 'Invalid user name or password.',
data:{}
})
} else {
res.send({
code:200,
message:'Success',
token: jwt.sign({username:userName},'abcd',{
expiresIn:10// 过期时间
}),
userName
})
}
}else{
res.status(401).send({
code:401,
message:'Username is empty',
data:{}
})
}
})
module.exports = router;