Commit a3687132b68188f5ffd4c53ebbff3a1835d3e291
1 parent
86e6c04e
增加 refresh token 接口,并接入到前端项目
Showing
13 changed files
with
154 additions
and
87 deletions
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; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.util.collection.SetUtils; |
| 7 | 7 | import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; |
| 8 | 8 | import cn.iocoder.yudao.framework.security.config.SecurityProperties; |
| 9 | -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | |
| 10 | 9 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; |
| 11 | 10 | import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; |
| 12 | 11 | import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; |
| ... | ... | @@ -34,6 +33,7 @@ import java.util.Set; |
| 34 | 33 | |
| 35 | 34 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 36 | 35 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 36 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization; | |
| 37 | 37 | import static java.util.Collections.singleton; |
| 38 | 38 | |
| 39 | 39 | @Api(tags = "管理后台 - 认证") |
| ... | ... | @@ -68,13 +68,20 @@ public class AuthController { |
| 68 | 68 | @ApiOperation("登出系统") |
| 69 | 69 | @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 |
| 70 | 70 | public CommonResult<Boolean> logout(HttpServletRequest request) { |
| 71 | - String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); | |
| 71 | + String token = obtainAuthorization(request, securityProperties.getTokenHeader()); | |
| 72 | 72 | if (StrUtil.isNotBlank(token)) { |
| 73 | 73 | authService.logout(token); |
| 74 | 74 | } |
| 75 | 75 | return success(true); |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | + @PostMapping("/refresh-token") | |
| 79 | + @ApiOperation("刷新令牌") | |
| 80 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 TODO 接口文档 | |
| 81 | + public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) { | |
| 82 | + return success(authService.refreshToken(refreshToken)); | |
| 83 | + } | |
| 84 | + | |
| 78 | 85 | @GetMapping("/get-permission-info") |
| 79 | 86 | @ApiOperation("获取登录用户的权限信息") |
| 80 | 87 | public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() { | ... | ... |
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; |
| 4 | 4 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; |
| 5 | 5 | import org.apache.ibatis.annotations.Mapper; |
| 6 | 6 | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 7 | 9 | @Mapper |
| 8 | 10 | public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> { |
| 9 | 11 | |
| ... | ... | @@ -11,22 +13,8 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO |
| 11 | 13 | return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken); |
| 12 | 14 | } |
| 13 | 15 | |
| 14 | -// default OAuth2AccessTokenDO selectByUserIdAndUserType(Integer userId, Integer userType) { | |
| 15 | -// return selectOne(new QueryWrapper<OAuth2AccessTokenDO>() | |
| 16 | -// .eq("user_id", userId).eq("user_type", userType)); | |
| 17 | -// } | |
| 18 | -// | |
| 19 | -// default int deleteByUserIdAndUserType(Integer userId, Integer userType) { | |
| 20 | -// return delete(new QueryWrapper<OAuth2AccessTokenDO>() | |
| 21 | -// .eq("user_id", userId).eq("user_type", userType)); | |
| 22 | -// } | |
| 23 | -// | |
| 24 | -// default int deleteByRefreshToken(String refreshToken) { | |
| 25 | -// return delete(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken)); | |
| 26 | -// } | |
| 27 | -// | |
| 28 | -// default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) { | |
| 29 | -// return selectList(new QueryWrapper<OAuth2AccessTokenDO>().eq("refresh_token", refreshToken)); | |
| 30 | -// } | |
| 16 | + default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) { | |
| 17 | + return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken); | |
| 18 | + } | |
| 31 | 19 | |
| 32 | 20 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.system.dal.mysql.auth; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 4 | 5 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; |
| 5 | -import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
| 6 | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 7 | |
| 8 | 8 | @Mapper |
| 9 | -public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> { | |
| 9 | +public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> { | |
| 10 | 10 | |
| 11 | 11 | default int deleteByRefreshToken(String refreshToken) { |
| 12 | 12 | return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>() |
| 13 | 13 | .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); |
| 14 | 14 | } |
| 15 | 15 | |
| 16 | + default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) { | |
| 17 | + return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken); | |
| 18 | + } | |
| 19 | + | |
| 16 | 20 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/auth/OAuth2AccessTokenRedisDAO.java
| 1 | 1 | package cn.iocoder.yudao.module.system.dal.redis.auth; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 4 | 5 | import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; |
| 5 | 6 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 6 | 7 | import org.springframework.stereotype.Repository; |
| 7 | 8 | |
| 8 | 9 | import javax.annotation.Resource; |
| 10 | +import java.util.Collection; | |
| 11 | +import java.util.List; | |
| 9 | 12 | import java.util.concurrent.TimeUnit; |
| 10 | 13 | |
| 11 | 14 | import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN; |
| ... | ... | @@ -39,6 +42,11 @@ public class OAuth2AccessTokenRedisDAO { |
| 39 | 42 | stringRedisTemplate.delete(redisKey); |
| 40 | 43 | } |
| 41 | 44 | |
| 45 | + public void deleteList(Collection<String> accessTokens) { | |
| 46 | + List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey); | |
| 47 | + stringRedisTemplate.delete(redisKeys); | |
| 48 | + } | |
| 49 | + | |
| 42 | 50 | private static String formatKey(String accessToken) { |
| 43 | 51 | return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken); |
| 44 | 52 | } | ... | ... |
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 { |
| 21 | 21 | // 登录的接口 |
| 22 | 22 | registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll(); |
| 23 | 23 | registry.antMatchers(buildAdminApi("/system/auth/logout")).permitAll(); |
| 24 | + registry.antMatchers(buildAdminApi("/system/auth/refresh-token")).permitAll(); | |
| 24 | 25 | // 社交登陆的接口 |
| 25 | 26 | registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll(); |
| 26 | 27 | registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll(); | ... | ... |
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 { |
| 59 | 59 | */ |
| 60 | 60 | AuthLoginRespVO socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); |
| 61 | 61 | |
| 62 | + /** | |
| 63 | + * 刷新访问令牌 | |
| 64 | + * | |
| 65 | + * @param refreshToken 刷新令牌 | |
| 66 | + * @return 登录结果 | |
| 67 | + */ | |
| 68 | + AuthLoginRespVO refreshToken(String refreshToken); | |
| 69 | + | |
| 62 | 70 | } | ... | ... |
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 { |
| 192 | 192 | return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); |
| 193 | 193 | } |
| 194 | 194 | |
| 195 | + @Override | |
| 196 | + public AuthLoginRespVO refreshToken(String refreshToken) { | |
| 197 | + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientIdEnum.DEFAULT.getId()); | |
| 198 | + return AuthConvert.INSTANCE.convert(accessTokenDO); | |
| 199 | + } | |
| 200 | + | |
| 195 | 201 | private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { |
| 196 | 202 | // 插入登陆日志 |
| 197 | 203 | createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); | ... | ... |
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 { |
| 30 | 30 | * 参考 DefaultTokenServices 的 refreshAccessToken 方法 |
| 31 | 31 | * |
| 32 | 32 | * @param refreshToken 刷新令牌 |
| 33 | + * @param clientId 客户端编号 | |
| 33 | 34 | * @return 访问令牌的信息 |
| 34 | 35 | */ |
| 35 | - OAuth2AccessTokenDO refreshAccessToken(String refreshToken); | |
| 36 | + OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId); | |
| 36 | 37 | |
| 37 | 38 | /** |
| 38 | 39 | * 获得访问令牌 | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.system.service.auth; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.IdUtil; |
| 5 | +import cn.hutool.core.util.ObjectUtil; | |
| 4 | 6 | import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; |
| 5 | 7 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 6 | 8 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| ... | ... | @@ -15,8 +17,10 @@ import org.springframework.transaction.annotation.Transactional; |
| 15 | 17 | |
| 16 | 18 | import javax.annotation.Resource; |
| 17 | 19 | import java.util.Calendar; |
| 20 | +import java.util.List; | |
| 18 | 21 | |
| 19 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 20 | 24 | |
| 21 | 25 | /** |
| 22 | 26 | * OAuth2.0 Token Service 实现类 |
| ... | ... | @@ -44,15 +48,38 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { |
| 44 | 48 | // 创建刷新令牌 |
| 45 | 49 | OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO); |
| 46 | 50 | // 创建访问令牌 |
| 47 | - OAuth2AccessTokenDO accessTokenDO = createOAuth2AccessToken(refreshTokenDO, clientDO); | |
| 48 | - // 记录到 Redis 中 | |
| 49 | - oauth2AccessTokenRedisDAO.set(accessTokenDO); | |
| 50 | - return accessTokenDO; | |
| 51 | + return createOAuth2AccessToken(refreshTokenDO, clientDO); | |
| 51 | 52 | } |
| 52 | 53 | |
| 53 | 54 | @Override |
| 54 | - public OAuth2AccessTokenDO refreshAccessToken(String refreshToken) { | |
| 55 | - return null; | |
| 55 | + public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, Long clientId) { | |
| 56 | + // 查询访问令牌 | |
| 57 | + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); | |
| 58 | + if (refreshTokenDO == null) { | |
| 59 | + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "无效的刷新令牌"); | |
| 60 | + } | |
| 61 | + | |
| 62 | + // 校验 Client 匹配 | |
| 63 | + OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); | |
| 64 | + if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) { | |
| 65 | + throw exception(GlobalErrorCodeConstants.BAD_REQUEST, "刷新令牌的客户端编号不正确"); | |
| 66 | + } | |
| 67 | + | |
| 68 | + // 移除相关的访问令牌 | |
| 69 | + List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); | |
| 70 | + if (CollUtil.isNotEmpty(accessTokenDOs)) { | |
| 71 | + oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); | |
| 72 | + oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); | |
| 73 | + } | |
| 74 | + | |
| 75 | + // 已过期的情况下,删除刷新令牌 | |
| 76 | + if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { | |
| 77 | + oauth2AccessTokenMapper.deleteById(refreshTokenDO.getId()); | |
| 78 | + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "刷新令牌已过期"); | |
| 79 | + } | |
| 80 | + | |
| 81 | + // 创建访问令牌 | |
| 82 | + return createOAuth2AccessToken(refreshTokenDO, clientDO); | |
| 56 | 83 | } |
| 57 | 84 | |
| 58 | 85 | @Override |
| ... | ... | @@ -76,10 +103,10 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { |
| 76 | 103 | public OAuth2AccessTokenDO checkAccessToken(String accessToken) { |
| 77 | 104 | OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken); |
| 78 | 105 | if (accessTokenDO == null) { |
| 79 | - throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 不存在"); | |
| 106 | + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌不存在"); | |
| 80 | 107 | } |
| 81 | 108 | if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) { |
| 82 | - throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "Token 已过期"); | |
| 109 | + throw exception(GlobalErrorCodeConstants.UNAUTHORIZED, "访问令牌已过期"); | |
| 83 | 110 | } |
| 84 | 111 | return accessTokenDO; |
| 85 | 112 | } |
| ... | ... | @@ -98,38 +125,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { |
| 98 | 125 | return accessTokenDO; |
| 99 | 126 | } |
| 100 | 127 | |
| 101 | - | |
| 102 | -// @Override | |
| 103 | -// @Transactional | |
| 104 | -// public OAuth2AccessTokenRespDTO refreshAccessToken(OAuth2RefreshAccessTokenReqDTO refreshAccessTokenDTO) { | |
| 105 | -// OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshAccessTokenDTO.getRefreshToken()); | |
| 106 | -// // 校验刷新令牌是否合法 | |
| 107 | -// if (refreshTokenDO == null) { // 不存在 | |
| 108 | -// throw ServiceExceptionUtil.exception(OAUTH2_REFRESH_TOKEN_NOT_FOUND); | |
| 109 | -// } | |
| 110 | -// if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 | |
| 111 | -// throw ServiceExceptionUtil.exception(OAUTH_REFRESH_TOKEN_EXPIRED); | |
| 112 | -// } | |
| 113 | -// | |
| 114 | -// // 标记 refreshToken 对应的 accessToken 都不合法 | |
| 115 | -// // 这块的实现,参考了 Spring Security OAuth2 的代码 | |
| 116 | -// List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshAccessTokenDTO.getRefreshToken()); | |
| 117 | -// accessTokenDOs.forEach(accessTokenDO -> deleteOAuth2AccessToken(accessTokenDO.getId())); | |
| 118 | -// | |
| 119 | -// // 创建访问令牌 | |
| 120 | -// OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); | |
| 121 | -// // 返回访问令牌 | |
| 122 | -// return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); | |
| 123 | -// } | |
| 124 | - | |
| 125 | 128 | private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { |
| 126 | - OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) | |
| 129 | + OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) | |
| 127 | 130 | .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()).setClientId(clientDO.getId()) |
| 128 | 131 | .setRefreshToken(refreshTokenDO.getRefreshToken()) |
| 129 | 132 | .setExpiresTime(DateUtils.addDate(Calendar.SECOND, clientDO.getAccessTokenValiditySeconds())); |
| 130 | - accessToken.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 | |
| 131 | - oauth2AccessTokenMapper.insert(accessToken); | |
| 132 | - return accessToken; | |
| 133 | + accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 | |
| 134 | + oauth2AccessTokenMapper.insert(accessTokenDO); | |
| 135 | + // 记录到 Redis 中 | |
| 136 | + oauth2AccessTokenRedisDAO.set(accessTokenDO); | |
| 137 | + return accessTokenDO; | |
| 133 | 138 | } |
| 134 | 139 | |
| 135 | 140 | private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO) { | ... | ... |
yudao-ui-admin/src/api/login.js
| 1 | 1 | import request from '@/utils/request' |
| 2 | +import {getRefreshToken} from "@/utils/auth"; | |
| 3 | +import service from "@/utils/request"; | |
| 2 | 4 | |
| 3 | 5 | // 登录方法 |
| 4 | 6 | export function login(username, password, code, uuid) { |
| ... | ... | @@ -99,3 +101,11 @@ export function smsLogin(mobile, code) { |
| 99 | 101 | } |
| 100 | 102 | }) |
| 101 | 103 | } |
| 104 | + | |
| 105 | +// 刷新访问令牌 | |
| 106 | +export function refreshToken() { | |
| 107 | + return service({ | |
| 108 | + url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(), | |
| 109 | + method: 'post' | |
| 110 | + }) | |
| 111 | +} | ... | ... |
yudao-ui-admin/src/store/modules/user.js
| ... | ... | @@ -3,8 +3,6 @@ import {getAccessToken, setToken, removeToken, getRefreshToken} from '@/utils/au |
| 3 | 3 | |
| 4 | 4 | const user = { |
| 5 | 5 | state: { |
| 6 | - accessToken: getAccessToken(), | |
| 7 | - refreshToken: getRefreshToken(), | |
| 8 | 6 | id: 0, // 用户编号 |
| 9 | 7 | name: '', |
| 10 | 8 | avatar: '', |
| ... | ... | @@ -16,10 +14,6 @@ const user = { |
| 16 | 14 | SET_ID: (state, id) => { |
| 17 | 15 | state.id = id |
| 18 | 16 | }, |
| 19 | - SET_TOKEN: (state, token) => { | |
| 20 | - state.accessToken = token.accessToken | |
| 21 | - state.refreshToken = token.refreshToken | |
| 22 | - }, | |
| 23 | 17 | SET_NAME: (state, name) => { |
| 24 | 18 | state.name = name |
| 25 | 19 | }, |
| ... | ... | @@ -46,7 +40,6 @@ const user = { |
| 46 | 40 | res = res.data; |
| 47 | 41 | // 设置 token |
| 48 | 42 | setToken(res) |
| 49 | - commit('SET_TOKEN', res) | |
| 50 | 43 | resolve() |
| 51 | 44 | }).catch(error => { |
| 52 | 45 | reject(error) |
| ... | ... | @@ -64,7 +57,6 @@ const user = { |
| 64 | 57 | res = res.data; |
| 65 | 58 | // 设置 token |
| 66 | 59 | setToken(res) |
| 67 | - commit('SET_TOKEN', res) | |
| 68 | 60 | resolve() |
| 69 | 61 | }).catch(error => { |
| 70 | 62 | reject(error) |
| ... | ... | @@ -84,7 +76,6 @@ const user = { |
| 84 | 76 | res = res.data; |
| 85 | 77 | // 设置 token |
| 86 | 78 | setToken(res) |
| 87 | - commit('SET_TOKEN', res) | |
| 88 | 79 | resolve() |
| 89 | 80 | }).catch(error => { |
| 90 | 81 | reject(error) |
| ... | ... | @@ -100,7 +91,6 @@ const user = { |
| 100 | 91 | res = res.data; |
| 101 | 92 | // 设置 token |
| 102 | 93 | setToken(res) |
| 103 | - commit('SET_TOKEN', res) | |
| 104 | 94 | resolve() |
| 105 | 95 | }).catch(error => { |
| 106 | 96 | reject(error) |
| ... | ... | @@ -148,7 +138,6 @@ const user = { |
| 148 | 138 | LogOut({ commit, state }) { |
| 149 | 139 | return new Promise((resolve, reject) => { |
| 150 | 140 | logout(state.token).then(() => { |
| 151 | - commit('SET_TOKEN', '') | |
| 152 | 141 | commit('SET_ROLES', []) |
| 153 | 142 | commit('SET_PERMISSIONS', []) |
| 154 | 143 | removeToken() | ... | ... |
yudao-ui-admin/src/utils/auth.js
yudao-ui-admin/src/utils/request.js
| 1 | 1 | import axios from 'axios' |
| 2 | 2 | import { Notification, MessageBox, Message } from 'element-ui' |
| 3 | 3 | import store from '@/store' |
| 4 | -import { getAccessToken } from '@/utils/auth' | |
| 4 | +import {getAccessToken, getRefreshToken, setToken} from '@/utils/auth' | |
| 5 | 5 | import errorCode from '@/utils/errorCode' |
| 6 | 6 | import Cookies from "js-cookie"; |
| 7 | 7 | import {getPath, getTenantEnable} from "@/utils/ruoyi"; |
| 8 | +import {refreshToken} from "@/api/login"; | |
| 8 | 9 | |
| 9 | 10 | // 是否显示重新登录 |
| 10 | 11 | export let isRelogin = { show: false }; |
| 12 | +// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 实现 | |
| 13 | +// 请求队列 | |
| 14 | +let requestList = [] | |
| 15 | +// 是否正在刷新中 | |
| 16 | +let isRefreshToken = false | |
| 11 | 17 | |
| 12 | 18 | axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' |
| 13 | 19 | // 创建axios实例 |
| ... | ... | @@ -60,29 +66,43 @@ service.interceptors.request.use(config => { |
| 60 | 66 | }) |
| 61 | 67 | |
| 62 | 68 | // 响应拦截器 |
| 63 | -service.interceptors.response.use(res => { | |
| 69 | +service.interceptors.response.use( async res => { | |
| 64 | 70 | // 未设置状态码则默认成功状态 |
| 65 | 71 | const code = res.data.code || 200; |
| 66 | 72 | // 获取错误信息 |
| 67 | 73 | const msg = errorCode[code] || res.data.msg || errorCode['default'] |
| 68 | 74 | if (code === 401) { |
| 69 | - if (!isRelogin.show) { | |
| 70 | - isRelogin.show = true; | |
| 71 | - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { | |
| 72 | - confirmButtonText: '重新登录', | |
| 73 | - cancelButtonText: '取消', | |
| 74 | - type: 'warning' | |
| 75 | - } | |
| 76 | - ).then(() => { | |
| 77 | - isRelogin.show = false; | |
| 78 | - store.dispatch('LogOut').then(() => { | |
| 79 | - location.href = getPath('/index'); | |
| 75 | + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 | |
| 76 | + if (!isRefreshToken) { | |
| 77 | + isRefreshToken = true; | |
| 78 | + // 1. 如果获取不到刷新令牌,则只能执行登出操作 | |
| 79 | + if (!getRefreshToken()) { | |
| 80 | + return handleAuthorized(); | |
| 81 | + } | |
| 82 | + // 2. 进行刷新访问令牌 | |
| 83 | + try { | |
| 84 | + const refreshTokenRes = await refreshToken() | |
| 85 | + // 2.1 刷新成功,则回放队列的请求 + 当前请求 | |
| 86 | + setToken(refreshTokenRes.data) | |
| 87 | + requestList.forEach(cb => cb()) | |
| 88 | + return service(res.config) | |
| 89 | + } catch (e) { | |
| 90 | + // 2.1 刷新失败,则只能执行登出操作 | |
| 91 | + return handleAuthorized(); | |
| 92 | + } finally { | |
| 93 | + requestList = [] | |
| 94 | + isRefreshToken = false | |
| 95 | + } | |
| 96 | + } else { | |
| 97 | + // 添加到队列,等待刷新获取到新的令牌 | |
| 98 | + return new Promise(resolve => { | |
| 99 | + requestList.push(() => { | |
| 100 | + config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 | |
| 101 | + resolve(service(config)) | |
| 80 | 102 | }) |
| 81 | - }).catch(() => { | |
| 82 | - isRelogin.show = false; | |
| 83 | - }); | |
| 103 | + }) | |
| 84 | 104 | } |
| 85 | - return Promise.reject('无效的会话,或者会话已过期,请重新登录。') | |
| 105 | + return handleAuthorized(); | |
| 86 | 106 | } else if (code === 500) { |
| 87 | 107 | Message({ |
| 88 | 108 | message: msg, |
| ... | ... | @@ -138,4 +158,24 @@ export function getBaseHeader() { |
| 138 | 158 | } |
| 139 | 159 | } |
| 140 | 160 | |
| 161 | +function handleAuthorized() { | |
| 162 | + if (!isRelogin.show) { | |
| 163 | + isRelogin.show = true; | |
| 164 | + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { | |
| 165 | + confirmButtonText: '重新登录', | |
| 166 | + cancelButtonText: '取消', | |
| 167 | + type: 'warning' | |
| 168 | + } | |
| 169 | + ).then(() => { | |
| 170 | + isRelogin.show = false; | |
| 171 | + store.dispatch('LogOut').then(() => { | |
| 172 | + location.href = getPath('/index'); | |
| 173 | + }) | |
| 174 | + }).catch(() => { | |
| 175 | + isRelogin.show = false; | |
| 176 | + }); | |
| 177 | + } | |
| 178 | + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') | |
| 179 | +} | |
| 180 | + | |
| 141 | 181 | export default service | ... | ... |