Commit c2937bd087dd638bcd392097408424010a4799b5

Authored by YunaiV
1 parent d5084424

【功能优化】SYSTEM:支持通过 refreshToken 认证,解决部分场景不方便刷新访问令牌场景

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.oauth2;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
5 5 import com.baomidou.mybatisplus.annotation.KeySequence;
6 6 import com.baomidou.mybatisplus.annotation.TableField;
7 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -24,7 +24,7 @@ import java.util.List;
24 24 @Data
25 25 @EqualsAndHashCode(callSuper = true)
26 26 @Accessors(chain = true)
27   -public class OAuth2RefreshTokenDO extends BaseDO {
  27 +public class OAuth2RefreshTokenDO extends TenantBaseDO {
28 28  
29 29 /**
30 30 * 编号,数据库字典
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.oauth2;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
5 6 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
6 7 import org.apache.ibatis.annotations.Mapper;
7 8  
... ... @@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
13 14 .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
14 15 }
15 16  
  17 + @TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
16 18 default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
17 19 return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
18 20 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java
... ... @@ -9,8 +9,10 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
9 9 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
10 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 11 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  12 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 13 import cn.iocoder.yudao.framework.security.core.LoginUser;
13 14 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  15 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
14 16 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
15 17 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
16 18 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
... ... @@ -105,10 +107,21 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
105 107 return accessTokenDO;
106 108 }
107 109  
108   - // 获取不到,从 MySQL 中获取
  110 + // 获取不到,从 MySQL 中获取访问令牌
109 111 accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
  112 + if (accessTokenDO != null && DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  113 + accessTokenDO = null;
  114 + }
  115 + // 特殊:从 MySQL 中获取刷新令牌。原因:解决部分场景不方便刷新访问令牌场景
  116 + // 例如说,积木报表只允许传递 token,不允许传递 refresh_token,导致无法刷新访问令牌
  117 + // 再例如说,前端 WebSocket 的 token 直接跟在 url 上,无法传递 refresh_token
  118 + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(accessToken);
  119 + if (refreshTokenDO != null && !DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
  120 + accessTokenDO = convertToAccessToken(refreshTokenDO);
  121 + }
  122 +
110 123 // 如果在 MySQL 存在,则往 Redis 中写入
111   - if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
  124 + if (accessTokenDO != null) {
112 125 oauth2AccessTokenRedisDAO.set(accessTokenDO);
113 126 }
114 127 return accessTokenDO;
... ... @@ -169,6 +182,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
169 182 return refreshToken;
170 183 }
171 184  
  185 + private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) {
  186 + OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class)
  187 + .setAccessToken(refreshTokenDO.getRefreshToken());
  188 + TenantUtils.execute(refreshTokenDO.getTenantId(),
  189 + () -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())));
  190 + return accessTokenDO;
  191 + }
  192 +
172 193 /**
173 194 * 加载用户信息,方便 {@link cn.iocoder.yudao.framework.security.core.LoginUser} 获取到昵称、部门等信息
174 195 *
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java
... ... @@ -232,6 +232,22 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
232 232 }
233 233  
234 234 @Test
  235 + public void testCheckAccessToken_refreshToken() {
  236 + // mock 数据(访问令牌)
  237 + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
  238 + .setExpiresTime(LocalDateTime.now().plusDays(1));
  239 + oauth2RefreshTokenMapper.insert(refreshTokenDO);
  240 + // 准备参数
  241 + String accessToken = refreshTokenDO.getRefreshToken();
  242 +
  243 + // 调研,并断言
  244 + OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);
  245 + // 断言
  246 + assertPojoEquals(refreshTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted",
  247 + "creator", "updater");
  248 + }
  249 +
  250 + @Test
235 251 public void testCheckAccessToken_success() {
236 252 // mock 数据(访问令牌)
237 253 OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
... ...