Bladeren bron

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

YunaiV 8 maanden geleden
bovenliggende
commit
c2937bd087

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.oauth2;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -24,7 +24,7 @@ import java.util.List;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @Accessors(chain = true)
-public class OAuth2RefreshTokenDO extends BaseDO {
+public class OAuth2RefreshTokenDO extends TenantBaseDO {
 
     /**
      * 编号,数据库字典

+ 2 - 0
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;
 
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
                 .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
     }
 
+    @TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
     default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
         return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
     }

+ 23 - 2
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;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.security.core.LoginUser;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
@@ -105,10 +107,21 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
             return accessTokenDO;
         }
 
-        // 获取不到,从 MySQL 中获取
+        // 获取不到,从 MySQL 中获取访问令牌
         accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
+        if (accessTokenDO != null && DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
+            accessTokenDO = null;
+        }
+        // 特殊:从 MySQL 中获取刷新令牌。原因:解决部分场景不方便刷新访问令牌场景
+        // 例如说,积木报表只允许传递 token,不允许传递 refresh_token,导致无法刷新访问令牌
+        // 再例如说,前端 WebSocket 的 token 直接跟在 url 上,无法传递 refresh_token
+        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(accessToken);
+        if (refreshTokenDO != null && !DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
+            accessTokenDO = convertToAccessToken(refreshTokenDO);
+        }
+
         // 如果在 MySQL 存在,则往 Redis 中写入
-        if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
+        if (accessTokenDO != null) {
             oauth2AccessTokenRedisDAO.set(accessTokenDO);
         }
         return accessTokenDO;
@@ -169,6 +182,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
         return refreshToken;
     }
 
+    private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) {
+        OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class)
+                .setAccessToken(refreshTokenDO.getRefreshToken());
+        TenantUtils.execute(refreshTokenDO.getTenantId(),
+                        () -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())));
+        return accessTokenDO;
+    }
+
     /**
      * 加载用户信息,方便 {@link cn.iocoder.yudao.framework.security.core.LoginUser} 获取到昵称、部门等信息
      *

+ 16 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java

@@ -231,6 +231,22 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
                 new ErrorCode(401, "访问令牌已过期"));
     }
 
+    @Test
+    public void testCheckAccessToken_refreshToken() {
+        // mock 数据(访问令牌)
+        OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
+                .setExpiresTime(LocalDateTime.now().plusDays(1));
+        oauth2RefreshTokenMapper.insert(refreshTokenDO);
+        // 准备参数
+        String accessToken = refreshTokenDO.getRefreshToken();
+
+        // 调研,并断言
+        OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);
+        // 断言
+        assertPojoEquals(refreshTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted",
+                "creator", "updater");
+    }
+
     @Test
     public void testCheckAccessToken_success() {
         // mock 数据(访问令牌)