Commit c2937bd087dd638bcd392097408424010a4799b5
1 parent
d5084424
【功能优化】SYSTEM:支持通过 refreshToken 认证,解决部分场景不方便刷新访问令牌场景
Showing
4 changed files
with
43 additions
and
4 deletions
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) |