浏览代码

Merge branch 'renewtoken'

zhangcl 1 周之前
父节点
当前提交
7ef77de575

+ 22 - 1
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java

@@ -21,6 +21,8 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Token 过滤器,验证 token 的有效性
@@ -31,6 +33,11 @@ import java.io.IOException;
 @RequiredArgsConstructor
 public class TokenAuthenticationFilter extends OncePerRequestFilter {
 
+    // 需要排除Token续期的接口路径
+    private static final List<String> EXCLUDE_REFRESH_URLS = Arrays.asList(
+            "/admin-api/system/notify-message/get-unread-count"
+    );
+
     private final SecurityProperties securityProperties;
 
     private final GlobalExceptionHandler globalExceptionHandler;
@@ -52,7 +59,11 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
                 if (loginUser == null) {
                     loginUser = mockLoginUser(request, token, userType);
                 }
-
+                // 延长双 token 过期时间 延长 30分钟
+                String requestURI = request.getRequestURI();
+                if (!EXCLUDE_REFRESH_URLS.contains(requestURI)) {
+                    renewTokenExpireTime(token);
+                }
                 // 2. 设置当前用户
                 if (loginUser != null) {
                     SecurityFrameworkUtils.setLoginUser(loginUser, request);
@@ -68,6 +79,16 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
         chain.doFilter(request, response);
     }
 
+    /**
+     * 延长 accessToken refreshToken 过期时间
+     * 只要在操作系统功能 token 就不会过期
+     * @param accessToken
+     */
+    private void renewTokenExpireTime(String accessToken) {
+        // 对当前token进行续期 延期
+        oauth2TokenApi.renewTokenExpireTime(accessToken);
+    }
+
     private LoginUser buildLoginUserByToken(String token, Integer userType) {
         try {
             OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);

+ 5 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/OAuth2TokenApi.java

@@ -46,4 +46,9 @@ public interface OAuth2TokenApi {
      */
     OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);
 
+    /**
+     * 续期双 token 的过期时间
+     * @param accessToken
+     */
+    void renewTokenExpireTime(String accessToken);
 }

+ 5 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/OAuth2TokenApiImpl.java

@@ -46,4 +46,9 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
         return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class);
     }
 
+    @Override
+    public void renewTokenExpireTime(String accessToken) {
+        oauth2TokenService.renewTokenExpireTime(accessToken);
+    }
+
 }

+ 16 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java

@@ -47,6 +47,22 @@ public class OAuth2AccessTokenRedisDAO {
         stringRedisTemplate.delete(redisKey);
     }
 
+    /**
+     * 续期 accessToken refreshToken
+     * @param accessToken
+     */
+    public void renewToken(String accessToken, String tokenJson, Long newTtl) {
+        String redisKey = formatKey(accessToken);
+        stringRedisTemplate.expire(redisKey, newTtl, TimeUnit.SECONDS);
+
+        stringRedisTemplate.opsForValue().set(
+                redisKey,
+                tokenJson,
+                newTtl,
+                TimeUnit.SECONDS
+        );
+    }
+
     public void deleteList(Collection<String> accessTokens) {
         List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);
         stringRedisTemplate.delete(redisKeys);

+ 6 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenService.java

@@ -77,4 +77,10 @@ public interface OAuth2TokenService {
      */
     PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO);
 
+    /**
+     * 续期 accessToken refreshToken
+     * @param accessToken
+     */
+    void renewTokenExpireTime(String accessToken);
+
 }

+ 36 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java

@@ -1,14 +1,17 @@
 package cn.iocoder.yudao.module.system.service.oauth2;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 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.json.JsonUtils;
 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;
@@ -28,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -158,6 +162,38 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
         return oauth2AccessTokenMapper.selectPage(reqVO);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void renewTokenExpireTime(String accessToken) {
+        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
+        if (accessTokenDO == null) {
+            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌不存在");
+        }
+
+        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
+            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌已过期");
+        }
+        LocalDateTime currentExpiresTime = accessTokenDO.getExpiresTime();
+        if (ObjUtil.isEmpty(currentExpiresTime)) {
+            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "访问令牌过期时间异常");
+        }
+        LocalDateTime currentTime = LocalDateTime.now();
+        // 计算新的过期时间(例如延长30分钟) 以当前时间为基准计算新的过期时间
+        LocalDateTime newExpiresTime = currentTime.plusMinutes(30);
+        long newTtl = LocalDateTimeUtil.between(LocalDateTime.now(), newExpiresTime, ChronoUnit.SECONDS);
+        if (newTtl > 0) {
+            accessTokenDO.setExpiresTime(newExpiresTime);
+            // 只有当accessTokenDO有id时才更新MySQL(避免从refreshToken转换而来的token)
+            if (ObjUtil.isNotEmpty(accessTokenDO.getId())) {
+                oauth2AccessTokenMapper.updateById(accessTokenDO);
+            }
+            // 更新Redis缓存
+            oauth2AccessTokenRedisDAO.set(accessTokenDO);
+        } else {
+            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "续期失败,新的过期时间无效");
+        }
+    }
+
     private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
         OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
                 .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())