From a3687132b68188f5ffd4c53ebbff3a1835d3e291 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 9 May 2022 22:46:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20refresh=20token=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=B9=B6=E6=8E=A5=E5=85=A5=E5=88=B0?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/auth/AuthController.java | 11 ++- .../mysql/auth/OAuth2AccessTokenMapper.java | 22 ++---- .../mysql/auth/OAuth2RefreshTokenMapper.java | 8 +- .../redis/auth/OAuth2AccessTokenRedisDAO.java | 8 ++ .../config/SecurityConfiguration.java | 1 + .../system/service/auth/AdminAuthService.java | 8 ++ .../service/auth/AdminAuthServiceImpl.java | 6 ++ .../service/auth/OAuth2TokenService.java | 3 +- .../service/auth/OAuth2TokenServiceImpl.java | 77 ++++++++++--------- yudao-ui-admin/src/api/login.js | 10 +++ yudao-ui-admin/src/store/modules/user.js | 11 --- yudao-ui-admin/src/utils/auth.js | 2 +- yudao-ui-admin/src/utils/request.js | 74 ++++++++++++++---- 13 files changed, 154 insertions(+), 87 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 153bc9c20..1b626eac3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; @@ -34,6 +33,7 @@ import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization; import static java.util.Collections.singleton; @Api(tags = "管理后台 - 认证") @@ -68,13 +68,20 @@ public class AuthController { @ApiOperation("登出系统") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult logout(HttpServletRequest request) { - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); + String token = obtainAuthorization(request, securityProperties.getTokenHeader()); if (StrUtil.isNotBlank(token)) { authService.logout(token); } return success(true); } + @PostMapping("/refresh-token") + @ApiOperation("刷新令牌") + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 TODO 接口文档 + public CommonResult refreshToken(@RequestParam("refreshToken") String refreshToken) { + return success(authService.refreshToken(refreshToken)); + } + @GetMapping("/get-permission-info") @ApiOperation("获取登录用户的权限信息") public CommonResult getPermissionInfo() { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java index 76d04d71a..c31ca7ee8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2AccessTokenMapper.java @@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface OAuth2AccessTokenMapper extends BaseMapperX { @@ -11,22 +13,8 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX() -// .eq("user_id", userId).eq("user_type", userType)); -// } -// -// default int deleteByUserIdAndUserType(Integer userId, Integer userType) { -// return delete(new QueryWrapper() -// .eq("user_id", userId).eq("user_type", userType)); -// } -// -// default int deleteByRefreshToken(String refreshToken) { -// return delete(new QueryWrapper().eq("refresh_token", refreshToken)); -// } -// -// default List selectListByRefreshToken(String refreshToken) { -// return selectList(new QueryWrapper().eq("refresh_token", refreshToken)); -// } + default List selectListByRefreshToken(String refreshToken) { + return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken); + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java index 145c7e9f8..0e46e4767 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java @@ -1,16 +1,20 @@ package cn.iocoder.yudao.module.system.dal.mysql.auth; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper -public interface OAuth2RefreshTokenMapper extends BaseMapper { +public interface OAuth2RefreshTokenMapper extends BaseMapperX { default int deleteByRefreshToken(String refreshToken) { return delete(new LambdaQueryWrapperX() .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); } + default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) { + return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java index cb7fba538..3afa69300 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java @@ -1,11 +1,14 @@ package cn.iocoder.yudao.module.system.dal.redis.auth; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN; @@ -39,6 +42,11 @@ public class OAuth2AccessTokenRedisDAO { stringRedisTemplate.delete(redisKey); } + public void deleteList(Collection accessTokens) { + List redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey); + stringRedisTemplate.delete(redisKeys); + } + private static String formatKey(String accessToken) { return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java index 9a7eb9728..1233a2d81 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java @@ -21,6 +21,7 @@ public class SecurityConfiguration { // 登录的接口 registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll(); registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll(); + registry.antMatchers(buildAdminApi("/system/auth/refresh-token")).permitAll(); // 社交登陆的接口 registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll(); registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll(); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 84bfe5e0f..3be7fdac1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -59,4 +59,12 @@ public interface AdminAuthService { */ AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); + /** + * 刷新访问令牌 + * + * @param refreshToken 刷新令牌 + * @return 登录结果 + */ + AuthLoginRespVO refreshToken(String refreshToken); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index b82ec951b..92098b7ca 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -192,6 +192,12 @@ public class AdminAuthServiceImpl implements AdminAuthService { return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); } + @Override + public AuthLoginRespVO refreshToken(String refreshToken) { + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId()); + return AuthConvert.INSTANCE.convert(accessTokenDO); + } + private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { // 插入登陆日志 createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 1811dcee0..20134fa0b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -30,9 +30,10 @@ public interface OAuth2TokenService { * 参考 DefaultTokenServices 的 refreshAccessToken 方法 * * @param refreshToken 刷新令牌 + * @param clientId 客户端编号 * @return 访问令牌的信息 */ - OAuth2AccessTokenDO refreshAccessToken(String refreshToken); + OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId); /** * 获得访问令牌 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java index 1aa07c442..609a3b0d7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.system.service.auth; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; @@ -15,8 +17,10 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Calendar; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; /** * OAuth2.0 Token Service 实现类 @@ -44,15 +48,38 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // 创建刷新令牌 OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); // 创建访问令牌 - OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO); - // 记录到 Redis 中 - oauth2AccessTokenRedisDAO.set(accessTokenDO); - return accessTokenDO; + return createOAuth2AccessToken(refreshTokenDO, clientDO); } @Override - public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) { - return null; + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) { + // 查询访问令牌 + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); + if (refreshTokenDO == null) { + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "无效的刷新令牌"); + } + + // 校验 Client 匹配 + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); + if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) { + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "刷新令牌的客户端编号不正确"); + } + + // 移除相关的访问令牌 + List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); + if (CollUtil.isNotEmpty(accessTokenDOs)) { + oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); + oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); + } + + // 已过期的情况下,删除刷新令牌 + if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { + oauth2AccessTokenMapper.deleteById(refreshTokenDO.getId()); + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "刷新令牌已过期"); + } + + // 创建访问令牌 + return createOAuth2AccessToken(refreshTokenDO, clientDO); } @Override @@ -76,10 +103,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { public OAuth2AccessTokenDO checkAccessToken(String accessToken) { OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken); if (accessTokenDO == null) { - throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在"); + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在"); } if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { - throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期"); + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期"); } return accessTokenDO; } @@ -98,38 +125,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { return accessTokenDO; } - -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) { -// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken()); -// // 校验刷新令牌是否合法 -// if (refreshTokenDO == null) { // 不存在 -// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND); -// } -// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 -// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED); -// } -// -// // 标记 refreshToken 对应的 accessToken 都不合法 -// // 这块的实现,参考了 Spring Security OAuth2 的代码 -// List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken()); -// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId())); -// -// // 创建访问令牌 -// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); -// } - private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { - OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) + OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId()) .setRefreshToken(refreshTokenDO.getRefreshToken()) .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); - accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 - oauth2AccessTokenMapper.insert(accessToken); - return accessToken; + accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 + oauth2AccessTokenMapper.insert(accessTokenDO); + // 记录到 Redis 中 + oauth2AccessTokenRedisDAO.set(accessTokenDO); + return accessTokenDO; } private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { diff --git a/yudao-ui-admin/src/api/login.js b/yudao-ui-admin/src/api/login.js index 77fbe9f9d..eb643e3a6 100644 --- a/yudao-ui-admin/src/api/login.js +++ b/yudao-ui-admin/src/api/login.js @@ -1,4 +1,6 @@ import request from '@/utils/request' +import {getRefreshToken} from "@/utils/auth"; +import service from "@/utils/request"; // 登录方法 export function login(username, password, code, uuid) { @@ -99,3 +101,11 @@ export function smsLogin(mobile, code) { } }) } + +// 刷新访问令牌 +export function refreshToken() { + return service({ + url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(), + method: 'post' + }) +} diff --git a/yudao-ui-admin/src/store/modules/user.js b/yudao-ui-admin/src/store/modules/user.js index 8fd3280ee..fbe209b75 100644 --- a/yudao-ui-admin/src/store/modules/user.js +++ b/yudao-ui-admin/src/store/modules/user.js @@ -3,8 +3,6 @@ import {getAccessToken, setToken, removeToken, getRefreshToken} from '@/utils/au const user = { state: { - accessToken: getAccessToken(), - refreshToken: getRefreshToken(), id: 0, // 用户编号 name: '', avatar: '', @@ -16,10 +14,6 @@ const user = { SET_ID: (state, id) => { state.id = id }, - SET_TOKEN: (state, token) => { - state.accessToken = token.accessToken - state.refreshToken = token.refreshToken - }, SET_NAME: (state, name) => { state.name = name }, @@ -46,7 +40,6 @@ const user = { res = res.data; // 设置 token setToken(res) - commit('SET_TOKEN', res) resolve() }).catch(error => { reject(error) @@ -64,7 +57,6 @@ const user = { res = res.data; // 设置 token setToken(res) - commit('SET_TOKEN', res) resolve() }).catch(error => { reject(error) @@ -84,7 +76,6 @@ const user = { res = res.data; // 设置 token setToken(res) - commit('SET_TOKEN', res) resolve() }).catch(error => { reject(error) @@ -100,7 +91,6 @@ const user = { res = res.data; // 设置 token setToken(res) - commit('SET_TOKEN', res) resolve() }).catch(error => { reject(error) @@ -148,7 +138,6 @@ const user = { LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { - commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() diff --git a/yudao-ui-admin/src/utils/auth.js b/yudao-ui-admin/src/utils/auth.js index 10818a50a..0c00919ed 100644 --- a/yudao-ui-admin/src/utils/auth.js +++ b/yudao-ui-admin/src/utils/auth.js @@ -8,7 +8,7 @@ export function getAccessToken() { } export function getRefreshToken() { - return Cookies.get(AccessTokenKey) + return Cookies.get(RefreshTokenKey) } export function setToken(token) { diff --git a/yudao-ui-admin/src/utils/request.js b/yudao-ui-admin/src/utils/request.js index ebd2de5e5..5da97c14e 100644 --- a/yudao-ui-admin/src/utils/request.js +++ b/yudao-ui-admin/src/utils/request.js @@ -1,13 +1,19 @@ import axios from 'axios' import { Notification, MessageBox, Message } from 'element-ui' import store from '@/store' -import { getAccessToken } from '@/utils/auth' +import {getAccessToken, getRefreshToken, setToken} from '@/utils/auth' import errorCode from '@/utils/errorCode' import Cookies from "js-cookie"; import {getPath, getTenantEnable} from "@/utils/ruoyi"; +import {refreshToken} from "@/api/login"; // 是否显示重新登录 export let isRelogin = { show: false }; +// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 实现 +// 请求队列 +let requestList = [] +// 是否正在刷新中 +let isRefreshToken = false axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // 创建axios实例 @@ -60,29 +66,43 @@ service.interceptors.request.use(config => { }) // 响应拦截器 -service.interceptors.response.use(res => { +service.interceptors.response.use( async res => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] if (code === 401) { - if (!isRelogin.show) { - isRelogin.show = true; - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { - confirmButtonText: '重新登录', - cancelButtonText: '取消', - type: 'warning' - } - ).then(() => { - isRelogin.show = false; - store.dispatch('LogOut').then(() => { - location.href = getPath('/index'); + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + if (!isRefreshToken) { + isRefreshToken = true; + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + if (!getRefreshToken()) { + return handleAuthorized(); + } + // 2. 进行刷新访问令牌 + try { + const refreshTokenRes = await refreshToken() + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + setToken(refreshTokenRes.data) + requestList.forEach(cb => cb()) + return service(res.config) + } catch (e) { + // 2.1 刷新失败,则只能执行登出操作 + return handleAuthorized(); + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise(resolve => { + requestList.push(() => { + config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(service(config)) }) - }).catch(() => { - isRelogin.show = false; - }); + }) } - return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + return handleAuthorized(); } else if (code === 500) { Message({ message: msg, @@ -138,4 +158,24 @@ export function getBaseHeader() { } } +function handleAuthorized() { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + } + ).then(() => { + isRelogin.show = false; + store.dispatch('LogOut').then(() => { + location.href = getPath('/index'); + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') +} + export default service