前端在access_token快过期时,刷新token,以及后端放行刷新token

This commit is contained in:
学生宫布 2020-08-01 14:39:53 +08:00
parent c0251e5cda
commit 46792c81f9
5 changed files with 465 additions and 254 deletions

View File

@ -11,8 +11,12 @@ import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.gateway.service.ValidateCodeService; import com.ruoyi.gateway.service.ValidateCodeService;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.List;
/** /**
* 验证码过滤器 * 验证码过滤器
* *
@ -38,8 +42,13 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
return (exchange, chain) -> { return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
// 非登录请求不处理 MultiValueMap<String, String> queryParams = request.getQueryParams();
if (!StringUtils.containsIgnoreCase(request.getURI().getPath(), AUTH_URL))
List<String> grant_typeS = queryParams.get("grant_type");
// 非登录请求不处理 刷新access_token不处理
boolean isLogin = StringUtils.containsIgnoreCase(request.getURI().getPath(),AUTH_URL);
if (!isLogin || (isLogin && !ObjectUtils.isEmpty(queryParams) && !ObjectUtils.isEmpty(grant_typeS) && grant_typeS.contains("refresh_token")))
{ {
return chain.filter(exchange); return chain.filter(exchange);
} }

View File

@ -1,39 +1,50 @@
import request from '@/utils/request' import request from '@/utils/request'
const client_id = 'web' const client_id = 'web'
const client_secret = '123456' const client_secret = '123456'
const grant_type = 'password' let grant_type = 'password'
const scope = 'server' const scope = 'server'
// 登录方法 // 刷新方法
export function login(username, password, code, uuid) { export function refreshToken( refresh_token ) {
return request({ grant_type = `refresh_token`
url: '/auth/oauth/token', return request({
method: 'post', url: '/auth/oauth/token',
params: { username, password, code, uuid, client_id, client_secret, grant_type, scope } method: 'post',
}) params: { client_id, client_secret, grant_type, scope, refresh_token }
} })
}
// 获取用户详细信息
export function getInfo() { // 登录方法
return request({ export function login(username, password, code, uuid) {
url: '/system/user/getInfo', grant_type = 'password'
method: 'get' return request({
}) url: '/auth/oauth/token',
} method: 'post',
params: { username, password, code, uuid, client_id, client_secret, grant_type, scope }
// 退出方法 })
export function logout() { }
return request({
url: '/auth/token/logout', // 获取用户详细信息
method: 'delete' export function getInfo() {
}) return request({
} url: '/system/user/getInfo',
method: 'get'
// 获取验证码 })
export function getCodeImg() { }
return request({
url: '/code', // 退出方法
method: 'get' export function logout() {
}) return request({
} url: '/auth/token/logout',
method: 'delete'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/code',
method: 'get'
})
}

View File

@ -1,96 +1,161 @@
import { login, logout, getInfo } from '@/api/login' import { login, logout, getInfo, refreshToken as refreshTokenFunc } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken,
setRefreshToken, removeRefreshToken,
const user = { setExpiresIn, removeExpiresIn
state: { } from '@/utils/auth'
token: getToken(),
name: '', /**
avatar: '', * 存储token
roles: [], * @param commit
permissions: [] * @param res
}, */
function storeToken(commit, resolve, res) {
mutations: { setToken(res.access_token)
SET_TOKEN: (state, token) => { commit('SET_TOKEN', res.access_token)
state.token = token
}, // 存储refresh_token expires_in
SET_NAME: (state, name) => { // console.log(`获取[刷新令牌]成功了 === `, res.refresh_token)
state.name = name setRefreshToken(res.refresh_token)
}, commit('SET_REFRESH_TOKEN', res.refresh_token)
SET_AVATAR: (state, avatar) => {
state.avatar = avatar const expires_in_time = new Date().getTime() + res.expires_in * 1000
}, // console.log(`获取[访问令牌]成功了,过期日期 === `, new Date(expires_in_time))
SET_ROLES: (state, roles) => { setExpiresIn(expires_in_time)
state.roles = roles commit('SET_EXPIRES_IN', expires_in_time)
},
SET_PERMISSIONS: (state, permissions) => { resolve()
state.permissions = permissions }
}
}, const user = {
state: {
actions: { token: getToken(),
// 登录 name: '',
Login({ commit }, userInfo) { avatar: '',
const username = userInfo.username.trim() roles: [],
const password = userInfo.password permissions: []
const code = userInfo.code },
const uuid = userInfo.uuid
return new Promise((resolve, reject) => { mutations: {
login(username, password, code, uuid).then(res => { SET_EXPIRES_IN: (state, v) => {
setToken(res.access_token) state.expires_in = v
commit('SET_TOKEN', res.access_token) },
resolve() SET_REFRESH_TOKEN: (state, v) => {
}).catch(error => { state.refresh_token = v
reject(error) },
}) SET_TOKEN: (state, token) => {
}) state.token = token
}, },
SET_NAME: (state, name) => {
// 获取用户信息 state.name = name
GetInfo({ commit, state }) { },
return new Promise((resolve, reject) => { SET_AVATAR: (state, avatar) => {
getInfo(state.token).then(res => { state.avatar = avatar
const user = res.user },
const avatar = user.avatar == "" ? require("@/assets/image/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; SET_ROLES: (state, roles) => {
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 state.roles = roles
commit('SET_ROLES', res.roles) },
commit('SET_PERMISSIONS', res.permissions) SET_PERMISSIONS: (state, permissions) => {
} else { state.permissions = permissions
commit('SET_ROLES', ['ROLE_DEFAULT']) }
} },
commit('SET_NAME', user.userName)
commit('SET_AVATAR', avatar)
resolve(res) actions: {
}).catch(error => {
reject(error) // 刷新
}) RefreshToken({ commit }, refreshTokenParams) {
}) // console.log(`进入src/store/modules/user.js执行[刷新token]`)
}, const refreshToken = refreshTokenParams.refreshToken
return new Promise((resolve, reject) => {
// 退出系统 refreshTokenFunc(refreshToken).then(res => {
LogOut({ commit, state }) { debugger
return new Promise((resolve, reject) => { // console.log(`调用[刷新token]接口,返回参数 === `, res)
logout(state.token).then(() => {
commit('SET_TOKEN', '') storeToken(commit, resolve, res)
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', []) }).catch(error => {
removeToken() reject(error)
resolve()
}).catch(error => { // console.log(`可能refresh_token已过期`, error)
reject(error)
}) // 清空
})
}, // console.log(`清空鉴权信息`)
commit('SET_TOKEN', '')
// 前端 登出 commit('SET_REFRESH_TOKEN', '')
FedLogOut({ commit }) { commit('SET_EXPIRES_IN', 0)
return new Promise(resolve => { commit('SET_ROLES', [])
commit('SET_TOKEN', '') commit('SET_PERMISSIONS', [])
removeToken() removeToken()
resolve() removeRefreshToken()
}) removeExpiresIn()
}
} })
} })
},
export default user
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
storeToken(commit, resolve, res)
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(res => {
const user = res.user
const avatar = user.avatar == "" ? require("@/assets/image/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_NAME', user.userName)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
export default user

View File

@ -1,15 +1,57 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token' const suffix = `ruoyi`
export function getToken() { const TokenKey = 'Admin-Token' + suffix
return Cookies.get(TokenKey) const RefreshTokenKey = 'Admin-Refresh-Token' + suffix
} const ExpiresInKey = 'Admin-Expires-In' + suffix
export function setToken(token) { export function getToken() {
return Cookies.set(TokenKey, token) return Cookies.get(TokenKey)
} }
export function removeToken() { export function setToken(v) {
return Cookies.remove(TokenKey) return Cookies.set(TokenKey, v)
} }
export function removeToken() {
return Cookies.remove(TokenKey)
}
/**
* 存储令牌信息 refresh_token expires_in 等等
* @param token
* @returns {*}
*/
export function getRefreshToken() {
// console.log(`从Cookie获取refresh_token`)
return Cookies.get(RefreshTokenKey) || ``
}
export function setRefreshToken(v) {
return Cookies.set(RefreshTokenKey, v)
}
export function removeRefreshToken() {
return Cookies.remove(RefreshTokenKey)
}
/**
*
* @returns {*}
*/
export function getExpiresIn() {
const time = Cookies.get(ExpiresInKey) || -1 // -1说明cookie没有过期时间用户还没有登录或者准备登录
// // console.log(`从Cookie获取token过期时间 === `, new Date(parseInt(time)))
return time
}
export function setExpiresIn(v) {
return Cookies.set(ExpiresInKey, v)
}
export function removeExpiresIn() {
return Cookies.remove(ExpiresInKey)
}

View File

@ -1,102 +1,186 @@
import axios from 'axios' import axios from 'axios'
import { Notification, MessageBox, Message } from 'element-ui' import { Notification, MessageBox, Message } from 'element-ui'
import store from '@/store' import store from '@/store'
import { getToken } from '@/utils/auth' import { getToken, getRefreshToken, getExpiresIn,removeToken} from '@/utils/auth'
import errorCode from '@/utils/errorCode' import errorCode from '@/utils/errorCode'
import { tansParams } from "@/utils/ruoyi"; import { tansParams } from "@/utils/ruoyi";
var refreshCount = 0
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // token将在这个时间以后过期 毫秒
// 创建axios实例 // const EXPIRED_IN_THIS_SECONDS = 6000
const service = axios.create({ const EXPIRED_IN_THIS_SECONDS = 100000
// axios中请求配置有baseURL选项表示请求URL公共部分 const CODE_PATH = `/code`
baseURL: process.env.VUE_APP_BASE_API, window.isRefreshing = false
// 超时
timeout: 10000 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
}) // 创建axios实例
const service = axios.create({
// request拦截器 // axios中请求配置有baseURL选项表示请求URL公共部分
service.interceptors.request.use(config => { baseURL: process.env.VUE_APP_BASE_API,
const isToken = (config.headers || {}).isToken === false // 超时
if (getToken() && !isToken) { timeout: 10000
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 })
}
return config /**
}, error => { * 增加令牌刷新功能
console.log(error) * qq8416837
Promise.reject(error) * author学生宫布
}) */
// request拦截器
// 响应拦截器 service.interceptors.request.use(config => {
service.interceptors.response.use(res => {
const code = res.data.code || 200; // 令牌维护 - start
const message = errorCode[code] || res.data.msg || errorCode['default'] // 获取当前时间戳,与过期时间比对,如果即将过期或已经过期,则调/auth/oauth/token API刷新token
if (code === 401) { // isRefreshing 检测是否正在刷新如果正在刷新则阻塞直到获取新token
MessageBox.confirm( const beLogining = config.url === CODE_PATH // 正在登录
'登录状态已过期,您可以继续留在该页面,或者重新登录', // console.log(`【请求拦截器执行中】`)
'系统提示', const futureTime = getExpiresIn()
{ // console.log(`令牌到期时间(long)`, futureTime)
confirmButtonText: '重新登录', // console.log(`令牌到期时间 === `, new Date(parseInt(futureTime)))
cancelButtonText: '取消', const itsTimeToRrefresh = futureTime != -1 && !window.isRefreshing && ((futureTime - new Date().getTime() ) <= EXPIRED_IN_THIS_SECONDS)
type: 'warning' if (itsTimeToRrefresh) { // 如果expires_in_time eq 0则很可能是初次登陆从而勿须刷新令牌 假如设置还差6秒过期
} // 锁 避免多个调用重复刷新
).then(() => { window.isRefreshing = true
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug // console.log(`当前时间 === `, new Date());
})
}) // console.log(`令牌`, (futureTime - new Date().getTime())/1000, `秒后过期,因此现在刷新令牌`);
} else if (code === 500) {
Message({ // 刷新令牌 将新令牌更新到存储或本地
message: message, return refresh(config);
type: 'error'
}) }
return Promise.reject(new Error(message)) // 令牌维护 - end
} else if (code !== 200) {
Notification.error({ else {
title: message // console.log(`令牌正常或者还未登录【或者正在刷新】因此暂不刷新它请求url === `, config.url);
})
return Promise.reject('error') if(beLogining) { // 如果正在登录那就清空token相关的cookie
} else { // console.log(`准备登录请求url === `, config.url)
return res.data removeToken()
} return config
}, }else { // 如果在调业务接口,则授权
error => { return auth(config); // 给请求添加token
console.log('err' + error) }
Message({ }
message: error.message,
type: 'error', }, error => {
duration: 5 * 1000 // console.log(error)
}) Promise.reject(error)
return Promise.reject(error) })
}
) /**
* 同步刷新令牌并更新到ajax配置
// 通用下载方法 * @param config
export function download(url, params, filename) { * @returns {Promise<*>}
return service.post(url, params, { */
transformRequest: [(params) => { async function refresh(config) {
return tansParams(params) const refreshTokenParams = {}
}], const refreshToken = getRefreshToken()
responseType: 'blob' refreshTokenParams.refreshToken = refreshToken
}).then((data) => { // 调API刷新令牌
const content = data await store.dispatch("RefreshToken", refreshTokenParams).then(() => {
const blob = new Blob([content])
if ('download' in document.createElement('a')) { auth(config)
const elink = document.createElement('a')
elink.download = filename })
elink.style.display = 'none' .catch((e) => {
elink.href = URL.createObjectURL(blob) // console.log(`刷新失败`, e, `,可能refresh_token已过期~`)
document.body.appendChild(elink) });
elink.click()
URL.revokeObjectURL(elink.href) refreshCount ++
document.body.removeChild(elink)
} else { // console.log(`刷新页面之前当前第几次刷新token === `, refreshCount)
navigator.msSaveBlob(blob, filename)
} window.isRefreshing = false
}).catch((r) => { return config
console.error(r) }
})
} /**
* 给请求授权
* @param config
* @returns {{headers}}
export default service */
function auth(config) {
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
// console.log(`访问`, config.url, `之前给请求头附加token`)
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}
// 响应拦截器
service.interceptors.response.use(res => {
const code = res.data.code || 200 || 0;
const message = errorCode[code] || res.data.errMsg || res.data.msg || errorCode['default']
if (code === 401) {
MessageBox.confirm(
'登录状态已过期,您可以继续留在该页面,或者重新登录',
'系统提示',
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
store.dispatch('LogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
})
} else if (code === 500) {
Message({
message: message,
type: 'error'
})
return Promise.reject(new Error(message))
} else if (code !== 200) {
Notification.error({
title: message
})
return Promise.reject('error')
} else {
return res.data
}
},
error => {
// console.log('err' + error)
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
// 通用下载方法
export function download(url, params, filename) {
return service.post(url, params, {
transformRequest: [(params) => {
return tansParams(params)
}],
responseType: 'blob'
}).then((data) => {
const content = data
const blob = new Blob([content])
if ('download' in document.createElement('a')) {
const elink = document.createElement('a')
elink.download = filename
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
} else {
navigator.msSaveBlob(blob, filename)
}
}).catch((r) => {
console.error(r)
})
}
export default service