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 package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; 1 package cn.iocoder.yudao.module.system.dal.dataobject.oauth2;
2 2
3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
6 import com.baomidou.mybatisplus.annotation.TableField; 6 import com.baomidou.mybatisplus.annotation.TableField;
7 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
@@ -24,7 +24,7 @@ import java.util.List; @@ -24,7 +24,7 @@ import java.util.List;
24 @Data 24 @Data
25 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
26 @Accessors(chain = true) 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,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.oauth2;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
5 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; 6 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
6 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
7 8
@@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken @@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
13 .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); 14 .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
14 } 15 }
15 16
  17 + @TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
16 default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) { 18 default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
17 return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken); 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,8 +9,10 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
9 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; 9 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 11 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  12 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 import cn.iocoder.yudao.framework.security.core.LoginUser; 13 import cn.iocoder.yudao.framework.security.core.LoginUser;
13 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 14 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  15 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
14 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; 16 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
15 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; 17 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
16 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; 18 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
@@ -105,10 +107,21 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -105,10 +107,21 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
105 return accessTokenDO; 107 return accessTokenDO;
106 } 108 }
107 109
108 - // 获取不到,从 MySQL 中获取 110 + // 获取不到,从 MySQL 中获取访问令牌
109 accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); 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 // 如果在 MySQL 存在,则往 Redis 中写入 123 // 如果在 MySQL 存在,则往 Redis 中写入
111 - if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { 124 + if (accessTokenDO != null) {
112 oauth2AccessTokenRedisDAO.set(accessTokenDO); 125 oauth2AccessTokenRedisDAO.set(accessTokenDO);
113 } 126 }
114 return accessTokenDO; 127 return accessTokenDO;
@@ -169,6 +182,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { @@ -169,6 +182,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
169 return refreshToken; 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 * 加载用户信息,方便 {@link cn.iocoder.yudao.framework.security.core.LoginUser} 获取到昵称、部门等信息 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,6 +232,22 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
232 } 232 }
233 233
234 @Test 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 public void testCheckAccessToken_success() { 251 public void testCheckAccessToken_success() {
236 // mock 数据(访问令牌) 252 // mock 数据(访问令牌)
237 OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class) 253 OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)