这篇文章写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带 Token 的形式。
开写之前先捋一下整理思路:
这里前端使用 Vue ,后端使用阿里的 egg
我把 Token 存在localStroage,检查有无 Token ,每次请求在 Axios 请求头上进行携带
if (window.localStorage.getItem('token')) {
Axios.defaults.headers.common['Authorization'] = `Bearer ` + window.localStorage.getItem('token')
}
使用 respone 拦截器,对 2xx 状态码以外的结果进行拦截。
如果状态码是401,则有可能是 Token 过期,跳转到登录页。
instance.interceptors.response.use(
response => {
return response
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
router.replace({
path: 'login',
query: { redirect: router.currentRoute.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
}
return Promise.reject(error.response)
}
)
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Index',
component: Index,
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Login
}
]
})
上面我给首页路由加了 requiresAuth,所以使用路由钩子来拦截导航,localStroage 里有 Token ,就调用获取 userInfo 的方法,并继续执行,如果没有 Token ,调用退出登录的方法,重定向到登录页。
router.beforeEach((to, from, next) => {
let token = window.localStorage.getItem('token')
if (to.meta.requiresAuth) {
if (token) {
store.dispatch('getUser')
next()
} else {
store.dispatch('logOut')
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
} else {
next()
}
})
这里使用了两个 Vuex 的 action 方法,马上就会说到 。
首先,在 mutation_types 里定义:
export const LOGIN = 'LOGIN' // 登录
export const USERINFO = 'USERINFO' // 用户信息
export const LOGINSTATUS = 'LOGINSTATUS' // 登录状态
然后在 mutation 里使用它们:
const mutations = {
[types.LOGIN]: (state, value) => {
state.token = value
},
[types.USERINFO]: (state, info) => {
state.userInfo = info
},
[types.LOGINSTATUS]: (state, bool) => {
state.loginStatus = bool
}
}
在之前封装 Axios 的 JS里定义请求接口:
export const login = ({ loginUser, loginPassword }) => {
return instance.post('/login', {
username: loginUser,
password: loginPassword
})
}
export const getUserInfo = () => {
return instance.get('/profile')
}
在 Vuex 的 actions 里引入:
import * as types from './types'
import { instance, login, getUserInfo } from '../api'
定义 action
export default {
toLogin ({ commit }, info) {
return new Promise((resolve, reject) => {
login(info).then(res => {
if (res.status === 200) {
commit(types.LOGIN, res.data.token) // 存储 token
commit(types.LOGINSTATUS, true) // 改变登录状态为
instance.defaults.headers.common['Authorization'] = `Bearer ` + res.data.token // 请求头添加 token
window.localStorage.setItem('token', res.data.token) // 存储进 localStroage
resolve(res)
}
}).catch((error) => {
console.log(error)
reject(error)
})
})
},
getUser ({ commit }) {
return new Promise((resolve, reject) => {
getUserInfo().then(res => {
if (res.status === 200) {
commit(types.USERINFO, res.data) // 把 userInfo 存进 Vuex
}
}).catch((error) => {
reject(error)
})
})
},
logOut ({ commit }) { // 退出登录
return new Promise((resolve, reject) => {
commit(types.USERINFO, null) // 情况 userInfo
commit(types.LOGINSTATUS, false) // 登录状态改为 false
commit(types.LOGIN, '') // 清除 token
window.localStorage.removeItem('token')
})
}
}
这时候,我们该去写后端接口了。
我这里用了阿里的 egg 框架,感觉很强大。
首先定义一个 LoginController :
const Controller = require('egg').Controller;
const jwt = require('jsonwebtoken'); // 引入 jsonwebtoken
class LoginController extends Controller {
async index() {
const ctx = this.ctx;
/*
把用户信息加密成 token ,因为没连接数据库,所以都是假数据
正常应该先判断用户名及密码是否正确
*/
const token = jwt.sign({
user_id: 1, // user_id
user_name: ctx.request.body.username // user_name
}, 'shenzhouhaotian', { // 秘钥
expiresIn: '60s' // 过期时间
});
ctx.body = { // 返回给前端
token: token
};
ctx.status = 200; // 状态码 200
}
}
module.exports = LoginController;
UserController:
class UserController extends Controller {
async index() {
const ctx = this.ctx
const authorization = ctx.get('Authorization');
if (authorization === '') { // 判断请求头有没有携带 token ,没有直接返回 401
ctx.throw(401, 'no token detected in http header "Authorization"');
}
const token = authorization.split(' ')[1];
// console.log(token)
let tokenContent;
try {
tokenContent = await jwt.verify(token, 'shenzhouhaotian'); //如果 token 过期或验证失败,将返回401
console.log(tokenContent)
ctx.body = tokenContent // token有效,返回 userInfo ;同理,其它接口在这里处理对应逻辑并返回
} catch (err) {
ctx.throw(401, 'invalid token');
}
}
}
在 router.js 里定义接口:
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/profile', controller.user.index);
router.post('/login', controller.login.index);
};
接口写好了,该前端去请求了。
这里我写了个登录组件,下面是点击登录时的 login 方法:
login () {
if (this.username === '') {
this.$message.warning('用户名不能为空哦~~')
} else if (this.password === '') {
this.$message.warning('密码不能为空哦~~')
} else {
this.$store.dispatch('toLogin', { // dispatch toLogin action
loginUser: this.username,
loginPassword: this.password
}).then(() => {
this.$store.dispatch('getUser') // dispatch getUserInfo action
let redirectUrl = decodeURIComponent(this.$route.query.redirect || '/')
console.log(redirectUrl)
// 跳转到指定的路由
this.$router.push({
path: redirectUrl
})
}).catch((error) => {
console.log(error.response.data.message)
})
}
}
登录成功后,跳转到首页之前重定向过来的页面。
整体流程跑完了,实现的主要功能就是:
以前的开发模式是以MVC为主,但是随着互联网行业快速的发展逐渐的演变成了前后端分离,若项目中需要做登录的话,那么token成为前后端唯一的一个凭证。token即标志、记号的意思,在IT领域也叫作令牌。
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库
最近遇到个需求:前端登录后,后端返回token和token有效时间,当token过期时要求用旧token去获取新的token,前端需要做到无痛刷新token,即请求刷新token时要做到用户无感知。
前端登录后,后端返回token和token有效时间段tokenExprieIn,当token过期时间到了,前端需要主动用旧token去获取一个新的token,做到用户无感知地去刷新token。
第一次登录的时候,前端调后端的登陆接口,发送用户名和密码,后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token,前端拿到token,将token存储到localStorage和vuex中
页面长时间未操作,再刷新页面时,第一次弹出“token失效,请重新登录!”提示,然后跳转到登录页面,接下来又弹出了n个“Token已过期”的后端返回消息提示。当前页面初始化,有多个向后端查询系统参数的调用,代码如下:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!