咱们在实际项目中选用了JWT
这种认证方式.前端
简单了解JWT
ios
JWT
即Json Web Token
, 用户登录后, 将非私密用户信息放置在token
中携带给前端并加密, 以后每一笔请求携带token
, 后端解密token
便可取得用户信息axios
最大好处: 后端无状态, 能够平滑横向扩张, 且
token
较难解密后端
最大弊端: 后端较难控制
token
失效跨域
是否须要前端刷新令牌?并发
其实并非必定的测试
Cookie
+HttpOnly
进行令牌传递, 可让前端无需操做任何令牌, 当令牌过时后主动由后端刷新并放回Cookie
便可Set-Cookie
时, Chrome
浏览器会发生没法正常处理Cookie
的问题, 所以放弃Cookie
传递, 使用请求头携带token
第一张图是
Chrome
, 第二张图是FireFox
, 相同请求网站
所以选用localStorage
存储token
, 由前端处理放在请求头中进行认证ui
最后一个问题: 因为token
有效期短, 须要有人刷新token
小插曲: 其实能够交给后端刷新令牌, 当
token
过时后, 后端刷新而后放回请求头, 前端主动根据返回请求头进行更新便可. 问题在后端遇到并发时token
会混乱.
最终缘由由前端刷新: 吵不事后端, 只能接下需求_(:з」∠)_
http
请求插件 不过多介绍, 选用了axios
axios
使用介绍 主要使用了拦截器interceptors
, 因为使用了Promise
, 能够随意在请求先后进行各类延时操做.
登录后将登录token
储存
axios
请求前将全部token
放入请求头
退出登陆时清空token
存储
当任意接口返回401
时, 尝试刷新token
, 若成功, 则更新token
储存, 若失败则跳转登录
因为并发的存在, 须要考虑如下状况:
token
, 将此请求拦截, 并在成功刷新后, 更新请求头并从新发送401
请求时, 若已经在尝试刷新token
, 将此请求拦截, 并在成功刷新后, 更新请求头并从新发送加载页面时, 尝试加载token
登陆后,将token
存储进localStorage
, 并更新全部axios
实例的默认请求头
null
退出登陆时, 将localStorage
的token
清除, 并清除全部axios
实例的默认请求头
axios
请求失败且状态码为401
时
刷新Token完成
事件
token刷新成功
时, 将该笔请求更新token
, 而后从新发送token刷新失败
时, 将该笔请求返回失败, 交由Promise:reject
进一步处理token
请求时, 尝试刷新token
, 并在刷新后触发刷新Token完成
事件, 若刷新成功, 则将token
存储进localStorage
, 并更新全部axios
实例的默认请求头axios
请求前, 检查是否正在刷新token
, 若正在刷新token
, 注册一个刷新Token完成
事件
token刷新成功
时, 将该笔请求更新token
, 而后继续发送token刷新失败
时, 将该笔请求返回失败, 抛弃请求加载页面时, 从localStorage
中获取token
私有全局变量
isRefreshing: Boolean
是否正在刷新token
RefreshEvent: EventEmitter
刷新事件分发器instances: AxiosInstance[]
全部封装好的axios
实例,
注意最好默认包含
Axios
即默认实例
REFRESH_URL: String
刷新URL封装方法
token
function setRefreshToken(refreshToken) {
if (refreshToken !== undefined) {
// 若携带参数, 则塞入localStorage中更新
localStorage.setItem('ompJwtRefreshToken', refreshToken)
} else {
// 若没有携带参数, 则从localStorage中加载, 注意防范XSS攻击
refreshToken = (
localStorage.getItem('ompJwtRefreshToken', refreshToken) || ''
).replace(/[^.\-_a-zA-Z0-9]/g, '')
}
// 这里instances包含全部axios实例
instances.forEach(instance => {
// 设置默认请求头
instance.defaults.headers['x-client-refresh-token'] = refreshToken
})
return refreshToken
}
复制代码
token
插播小广告: 个人掘金主页
function tryToRefreshToken() {
// 约全局变量isRefreshing: 是否正在刷新token
isRefreshing = true
let refreshToken = localStorage.getItem('ompJwtRefreshToken') || ''
refreshToken = refreshToken.replace(/[^.\-_a-zA-Z0-9]/g, '')
// 没有refreshToken是没法刷新token的, 直接失败
// 这里使用事件分发机制处理, 返回false表示刷新失败
if (!refreshToken) return RefreshEvent.emit('refreshEnd', false)
// ^_^ 友好提示, 防止用户觉得刷新中点击按钮没有反应
Notice.open({
title: '正在主动刷新, 尝试继续登录中……',
duration: 0,
name: 'refresh'
})
axios
.get(REFRESH_URL)
.then(res => {
// 刷新成功
Notice.close('refresh')
Notice.open({
title: '刷新成功, 将自动继续您以前的操做~'
})
// 注意触发事件
RefreshEvent.emit('refreshEnd', true)
})
.catch(e => {
// 刷新失败
RefreshEvent.emit('refreshEnd', false)
Notice.close('refresh')
Notice.open({
title: '刷新失败, 将自动为您跳转登陆页'
})
router.push({name: LOGIN_PAGE})
return Promise.reject(e)
})
}
复制代码
function preRequestInterceptor(config) {
// 当正在刷新token时, 延时请求, 直到刷新完成
if (isRefreshing && config.url !== REFRESH_URL) {
// 经过返回Promise进行延迟操做
return new Promise((resolve, reject) => {
// 注册事件
RefreshEvent.once('refreshEnd', result => {
// 注意resolve(config)才能继续请求
// 注意config中已经包含旧的token了, 而且不会自动刷新, 须要手动从新设置下
if (result) {
config.headers['x-client-refresh-token'] = setRefreshToken()
resolve(config)
}
// 这里建议reject封装后的东西, 不然会出现reject形式不一致
else reject(config)
})
})
}
return config
}
复制代码
function errorDeal(error) {
if (error && error.response) {
switch (error.response.status) {
// 通常会有其余处理吧
case 401:
// 绑定事件
// 重发事件避免重复处理(其实不会出现这种状况)
if (error.config._retry) return Promise.reject(error)
// 先注册事件!!! 再触发重试, 不然可能会注册失败哦~
const re = new Promise((resolve, reject) => {
// 一样注册事件, 用于延时请求
RefreshEvent.once('refreshEnd', result => {
if (result) resolve(error.config)
else reject(error)
})
}).then(config =>
// 两个注意点:
// 1. 刷新token
// 2. 请使用Axios.create({})出的实例, 避免此请求重复一次错误处理, 那样的话就会有两次错误处理
config.headers['x-client-refresh-token'] = setRefreshToken()
axiosRetry.request({
...config,
// 绑定一些私有属性方便大家使用
_retry: true
})
)
if (!isRefreshing) tryToRefreshToken()
// 注意返回Promise
return re
}
}
return Promise.reject(error)
}
复制代码
在各个地方触发各类方法:
// 定义变量哟~
// 注册刷新结束时间, 解除刷新态, 路由处理
RefreshEvent.on('refreshEnd', result => {
isRefreshing = false
if (!result) {
route.push({
name: 'login'
})
}
})
// 绑定拦截器
instances.forEach(instance => {
instance.interceptors.request.use(preRequestInterceptor)
instance.interceptors.response.use(undefined, errorDeal)
})
// 注册重试实例(即不注册拦截器)
const axiosRetry = axios.create({})
// 先触发一下, 以便从localStorage中进行加载
setAccessToken()
setRefreshToken()
复制代码
分享一下工做时缥缈的想法, 没有而后了.
若是文中出现错误, 还请提出哟, 我尽可能改~
插播小广告: 个人掘金主页
P.S. 坐标: 南京, 性别: ♂, 联系方式: 41620F678
此文在掘金原创, 其余网站请勿转载.