소스 검색

代码优化

woody 1 년 전
부모
커밋
028e2d9567
15개의 변경된 파일251개의 추가작업 그리고 293개의 파일을 삭제
  1. 1 1
      framework-base/src/main/java/com/chelvc/framework/base/config/CopyingConfigurer.java
  2. 63 68
      framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java
  3. 5 5
      framework-base/src/main/java/com/chelvc/framework/base/model/Session.java
  4. 1 1
      framework-boot/pom.xml
  5. 70 16
      framework-common/src/main/java/com/chelvc/framework/common/util/IdentityUtils.java
  6. 0 2
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicDatasourceInterceptor.java
  7. 1 1
      framework-dubbo/src/main/java/com/chelvc/framework/dubbo/adapter/DubboObjectAdapterHolder.java
  8. 1 1
      framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java
  9. 21 31
      framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthConfigurer.java
  10. 5 0
      framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthProperties.java
  11. 47 154
      framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java
  12. 13 6
      framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenActiveValidator.java
  13. 20 4
      framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenStore.java
  14. 1 1
      framework-redis/src/main/java/com/chelvc/framework/redis/config/RedisConfigurer.java
  15. 2 2
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java

+ 1 - 1
framework-base/src/main/java/com/chelvc/framework/base/config/CopyingConfigurer.java

@@ -25,7 +25,7 @@ public class CopyingConfigurer implements ApplicationContextAware {
         Collection<CopierFactoryConfigurer> configurers =
                 applicationContext.getBeansOfType(CopierFactoryConfigurer.class).values();
         if (!CollectionUtils.isEmpty(configurers)) {
-            log.info("Loading object copier configurers: {}", configurers);
+            log.info("Initialize object copier configurers: {}", configurers);
             MapperFactory factory = ObjectUtils.getCopierBuilder();
             configurers.forEach(configurer -> configurer.configure(factory));
         }

+ 63 - 68
framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java

@@ -48,6 +48,11 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_ID = "Id";
 
+    /**
+     * 应用范围请求头
+     */
+    public static final String HEADER_SCOPE = "Scope";
+
     /**
      * 电话号码请求头
      */
@@ -83,11 +88,6 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_VERSION = "Version";
 
-    /**
-     * 业务类型请求头
-     */
-    public static final String HEADER_BUSINESS = "Business";
-
     /**
      * 授权信息请求头
      */
@@ -108,11 +108,6 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_FINGERPRINT = "Fingerprint";
 
-    /**
-     * 实际终端编号请求头
-     */
-    public static final String HEADER_ACTUAL_TERMINAL = "Actual-Terminal";
-
     /**
      * 响应结果包装请求/响应头
      */
@@ -203,6 +198,25 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getHost);
     }
 
+    /**
+     * 获取当前应用范围
+     *
+     * @return 应用范围
+     */
+    public static String getScope() {
+        return getScope(true);
+    }
+
+    /**
+     * 获取当前应用范围
+     *
+     * @param requireSession 会话是否是必须
+     * @return 应用范围
+     */
+    public static String getScope(boolean requireSession) {
+        return ObjectUtils.ifNull(getSession(requireSession), Session::getScope);
+    }
+
     /**
      * 获取当前会话电话号码
      *
@@ -317,25 +331,6 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getVersion);
     }
 
-    /**
-     * 获取当前业务类型
-     *
-     * @return 业务类型
-     */
-    public static String getBusiness() {
-        return getBusiness(true);
-    }
-
-    /**
-     * 获取当前业务类型
-     *
-     * @param requireSession 会话是否是必须
-     * @return 业务类型
-     */
-    public static String getBusiness(boolean requireSession) {
-        return ObjectUtils.ifNull(getSession(requireSession), Session::getBusiness);
-    }
-
     /**
      * 获取当前会话授权信息
      *
@@ -484,6 +479,16 @@ public class SessionContextHolder implements ServletRequestListener {
         }
     }
 
+    /**
+     * 判断是否是指定应用范围
+     *
+     * @param scope 应用范围
+     * @return true/false
+     */
+    public static boolean isScope(String scope) {
+        return StringUtils.nonEmpty(scope) && Objects.equals(scope, getScope(false));
+    }
+
     /**
      * 判断当前平台是否是指定平台
      *
@@ -606,16 +611,6 @@ public class SessionContextHolder implements ServletRequestListener {
                 && Version.isAfter(ObjectUtils.ifNull(session, Session::getVersion), version, contains);
     }
 
-    /**
-     * 判断是否是指定业务类型
-     *
-     * @param business 业务类型
-     * @return true/false
-     */
-    public static boolean isBusiness(String business) {
-        return StringUtils.nonEmpty(business) && Objects.equals(business, getBusiness(false));
-    }
-
     /**
      * 判断是否是测试账号
      *
@@ -715,6 +710,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return StringUtils.ifEmpty(HttpUtils.getRequestAddress(request), (String) null);
     }
 
+    /**
+     * 获取应用范围
+     *
+     * @param request Http请求对象
+     * @return 应用范围
+     */
+    protected String getScope(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_SCOPE), (String) null);
+    }
+
     /**
      * 获取会话电话号码
      *
@@ -775,16 +780,6 @@ public class SessionContextHolder implements ServletRequestListener {
         return StringUtils.ifEmpty(request.getHeader(HEADER_VERSION), (String) null);
     }
 
-    /**
-     * 获取业务类型
-     *
-     * @param request Http请求对象
-     * @return 业务类型
-     */
-    protected String getBusiness(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_BUSINESS), (String) null);
-    }
-
     /**
      * 获取客户端授权信息
      *
@@ -822,10 +817,10 @@ public class SessionContextHolder implements ServletRequestListener {
      * @return 会话信息
      */
     protected Session initializeSession(@NonNull HttpServletRequest request) {
-        return Session.builder().id(this.getId(request)).host(this.getHost(request)).mobile(this.getMobile(request))
-                .device(this.getDevice(request)).channel(this.getChannel(request)).platform(this.getPlatform(request))
-                .terminal(this.getTerminal(request)).version(this.getVersion(request))
-                .business(this.getBusiness(request)).authority(this.getAuthority(request))
+        return Session.builder().id(this.getId(request)).host(this.getHost(request)).scope(this.getScope(request))
+                .mobile(this.getMobile(request)).device(this.getDevice(request)).channel(this.getChannel(request))
+                .platform(this.getPlatform(request)).terminal(this.getTerminal(request))
+                .version(this.getVersion(request)).authority(this.getAuthority(request))
                 .initial(this.getInitial(request)).timestamp(this.getTimestamp(request)).build();
     }
 
@@ -843,56 +838,56 @@ public class SessionContextHolder implements ServletRequestListener {
     /**
      * 初始化会话主体
      *
-     * @param id       主体标识
-     * @param business 业务类型
+     * @param id    主体标识
+     * @param scope 应用范围
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String business) {
-        return initializeSessionPrincipal(id, business, null, null, true);
+    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope) {
+        return initializeSessionPrincipal(id, scope, null, null, true);
     }
 
     /**
      * 初始化会话主体
      *
-     * @param id       主体标识
-     * @param business 业务类型
-     * @param cover    是否覆盖已认证主体信息
+     * @param id    主体标识
+     * @param scope 应用范围
+     * @param cover 是否覆盖已认证主体信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String business, boolean cover) {
-        return initializeSessionPrincipal(id, business, null, null, cover);
+    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, boolean cover) {
+        return initializeSessionPrincipal(id, scope, null, null, cover);
     }
 
     /**
      * 初始化会话主体
      *
      * @param id        主体标识
-     * @param business  业务类型
+     * @param scope     应用范围
      * @param mobile    电话号码
      * @param authority 授权信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String business, String mobile,
+    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, String mobile,
                                                      String authority) {
-        return initializeSessionPrincipal(id, business, mobile, authority, true);
+        return initializeSessionPrincipal(id, scope, mobile, authority, true);
     }
 
     /**
      * 初始化会话主体
      *
      * @param id        主体标识
-     * @param business  业务类型
+     * @param scope     应用范围
      * @param mobile    电话号码
      * @param authority 授权信息
      * @param cover     是否覆盖已认证主体信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String business, String mobile,
+    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, String mobile,
                                                      String authority, boolean cover) {
         Deque<Session> deque = SESSION_CONTEXT.get();
         Session session = deque.peek();
         if (cover || ObjectUtils.ifNull(session, Session::getId) == null) {
-            session = Session.builder().id(id).business(business).mobile(mobile).authority(authority)
+            session = Session.builder().id(id).scope(scope).mobile(mobile).authority(authority)
                     .host(ObjectUtils.ifNull(session, Session::getHost))
                     .device(ObjectUtils.ifNull(session, Session::getDevice))
                     .channel(ObjectUtils.ifNull(session, Session::getChannel))

+ 5 - 5
framework-base/src/main/java/com/chelvc/framework/base/model/Session.java

@@ -30,6 +30,11 @@ public class Session implements Serializable {
      */
     private String host;
 
+    /**
+     * 应用范围
+     */
+    private String scope;
+
     /**
      * 电话号码
      */
@@ -60,11 +65,6 @@ public class Session implements Serializable {
      */
     private String version;
 
-    /**
-     * 业务类型
-     */
-    private String business;
-
     /**
      * 授权信息
      */

+ 1 - 1
framework-boot/pom.xml

@@ -63,7 +63,7 @@
                 <artifactId>apidoc-maven-plugin</artifactId>
                 <version>${apidoc-maven-plugin.version}</version>
                 <configuration>
-                    <includeHeaders>{Long} Id 主体标识,{String} Mobile 手机号码,{String} Device 设备标识,{String} Channel 渠道来源,{String} Platform 平台标识: PC(PC)、IOS(苹果)、ANDROID(安卓),{String} Terminal 终端标识: WEB(Web)、APP(App)、APPLET(小程序),{String} Actual-Terminal 实际终端标识: WEB(Web)、APP(App)、APPLET(小程序),{String} Version 终端版本,{String} Business 业务类型,{String} Authority 授权信息,{Boolean} Initial 是否首次请,{Long} Timestamp 请求时间戳,{String} Signature 签名信息,{String} Fingerprint 设备指纹,{String} Authorization 认证信息</includeHeaders>
+                    <includeHeaders>{Long} Id 主体标识,{String} Scope 应用范围,{String} Mobile 手机号码,{String} Device 设备标识,{String} Channel 渠道来源,{String} Platform 平台标识: PC(PC)、IOS(苹果)、ANDROID(安卓),{String} Terminal 终端标识: WEB(Web)、APP(App)、APPLET(小程序),{String} Version 终端版本,{String} Authority 授权信息,{Boolean} Initial 是否首次请,{Long} Timestamp 请求时间戳,{String} Signature 签名信息,{String} Fingerprint 设备指纹,{String} Authorization 认证信息</includeHeaders>
                     <excludeClasses>com.chelvc.framework.base.interceptor.GlobalExceptionInterceptor</excludeClasses>
                     <analyserFactoryClass>com.chelvc.framework.base.apidoc.MethodAnalyserFactory</analyserFactoryClass>
                 </configuration>

+ 70 - 16
framework-common/src/main/java/com/chelvc/framework/common/util/IdentityUtils.java

@@ -1,6 +1,8 @@
 package com.chelvc.framework.common.util;
 
-import java.util.Random;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.util.concurrent.ThreadLocalRandom;
 
 import lombok.extern.slf4j.Slf4j;
@@ -16,13 +18,15 @@ public final class IdentityUtils {
     /**
      * 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
      */
-    private static final long BENCHMARK = 1693154100782L;
+    private static final long BENCHMARK = 1288834974657L;
 
     /**
      * 机器标识位数
      */
     private static final long WORKER_ID_BITS = 5L;
     private static final long DATACENTER_ID_BITS = 5L;
+    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
+    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
 
     /**
      * 毫秒内自增位
@@ -43,14 +47,14 @@ public final class IdentityUtils {
     private static final long CLOCK_MOVED_BACKWARD_OFFSET = 5;
 
     /**
-     * 机器标识
+     * 数据标识 ID 部分
      */
-    private static final long WORKER_ID = new Random().nextInt(32);
+    private static final long DATACENTER_ID = initializeDatacenterId(MAX_DATACENTER_ID);
 
     /**
-     * 数据标识 ID 部分
+     * 机器标识
      */
-    private static final long DATACENTER_ID = new Random().nextInt(32);
+    private static final long WORKER_ID = initializeWorkerId(DATACENTER_ID, MAX_WORKER_ID);
 
     /**
      * 并发控制
@@ -62,9 +66,69 @@ public final class IdentityUtils {
      */
     private long lastTimestamp = -1L;
 
+    /**
+     * 唯一ID工具实例构造类
+     */
+    private static final class Builder {
+        /**
+         * 工具实例
+         */
+        private static final IdentityUtils INSTANCE = new IdentityUtils();
+    }
+
     private IdentityUtils() {
     }
 
+    /**
+     * 初始化数据标识ID
+     *
+     * @param maxDatacenterId 最大数据标识ID
+     * @return 数据标识ID
+     */
+    private static long initializeDatacenterId(long maxDatacenterId) {
+        long id = 0L;
+        try {
+            InetAddress ip = InetAddress.getLocalHost();
+            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
+            if (network == null) {
+                id = 1L;
+            } else {
+                byte[] mac = network.getHardwareAddress();
+                if (null != mac) {
+                    id = ((0x000000FF & (long) mac[mac.length - 2])
+                            | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
+                    id = id % (maxDatacenterId + 1);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("Datacenter id initialize failed: {}", e.getMessage());
+        }
+        return id;
+    }
+
+    /**
+     * 初始化机器标识ID
+     *
+     * @param datacenterId 数据标识ID
+     * @param maxWorkerId  最大机器标识ID
+     * @return 机器标识ID
+     */
+    private static long initializeWorkerId(long datacenterId, long maxWorkerId) {
+        StringBuilder pid = new StringBuilder();
+        pid.append(datacenterId);
+        String name = ManagementFactory.getRuntimeMXBean().getName();
+        if (StringUtils.nonBlank(name)) {
+            /*
+             * GET jvmPid
+             */
+            pid.append(name.split("@")[0]);
+        }
+        /*
+         * MAC + PID 的 hashcode 获取16个低位
+         */
+        return (pid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
+    }
+
     /**
      * 获取当前时间戳
      *
@@ -145,14 +209,4 @@ public final class IdentityUtils {
                 | (WORKER_ID << WORKER_ID_SHIFT)
                 | this.sequence;
     }
-
-    /**
-     * 唯一ID工具实例构造类
-     */
-    private static final class Builder {
-        /**
-         * 工具实例
-         */
-        private static final IdentityUtils INSTANCE = new IdentityUtils();
-    }
 }

+ 0 - 2
framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicDatasourceInterceptor.java

@@ -19,7 +19,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
 import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
 import org.springframework.beans.factory.support.RootBeanDefinition;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.core.annotation.Order;
 import org.springframework.util.CollectionUtils;
 
@@ -35,7 +34,6 @@ import org.springframework.util.CollectionUtils;
  * @date 2023/4/5
  */
 @Slf4j
-@ConditionalOnClass(DynamicDataSourceContextHolder.class)
 public class DynamicDatasourceInterceptor implements BeanDefinitionRegistryPostProcessor {
     @Override
     public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {

+ 1 - 1
framework-dubbo/src/main/java/com/chelvc/framework/dubbo/adapter/DubboObjectAdapterHolder.java

@@ -33,7 +33,7 @@ public class DubboObjectAdapterHolder implements ApplicationContextAware {
         Collection<DubboObjectAdapter> adapters =
                 applicationContext.getBeansOfType(DubboObjectAdapter.class).values();
         if (!CollectionUtils.isEmpty(adapters)) {
-            log.info("Loading dubbo object adapters: {}", adapters);
+            log.info("Initialize dubbo object adapters: {}", adapters);
             CLASS_WRAPPER_MAPPING = Maps.newHashMapWithExpectedSize(adapters.size());
             adapters.forEach(adapter -> {
                 DubboObjectAdapterWrapper<?, ?> wrapper = this.adapter2wrapper(adapter);

+ 1 - 1
framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java

@@ -42,8 +42,8 @@ public class FeignHeaderInterceptor implements RequestInterceptor {
         Session session = SessionContextHolder.getSession(false);
         if (session != null) {
             headers.put(SessionContextHolder.HEADER_ID, session.getId());
+            headers.put(SessionContextHolder.HEADER_SCOPE, session.getScope());
             headers.put(SessionContextHolder.HEADER_MOBILE, session.getMobile());
-            headers.put(SessionContextHolder.HEADER_BUSINESS, session.getBusiness());
             headers.put(SessionContextHolder.HEADER_AUTHORITY, session.getAuthority());
         }
 

+ 21 - 31
framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthConfigurer.java

@@ -11,19 +11,16 @@ import com.chelvc.framework.base.model.Result;
 import com.chelvc.framework.common.util.AESUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.oauth.context.OauthContextHolder;
-import com.chelvc.framework.oauth.token.RedisTokenActiveValidator;
 import com.chelvc.framework.oauth.token.TokenActiveValidator;
 import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.io.ClassPathResource;
-import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.authentication.AuthenticationManager;
@@ -34,6 +31,7 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -105,25 +103,19 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
 
         // 添加自定义令牌验证器
         Collection<OAuth2TokenValidator<Jwt>> validators = Lists.newLinkedList();
-        validators.add(new OAuth2TokenValidator<Jwt>() {
-            private final JwtTimestampValidator timestampValidator = new JwtTimestampValidator();
-
-            @Override
-            public OAuth2TokenValidatorResult validate(Jwt jwt) {
-                // 初始化会话主体信息
-                SessionContextHolder.initializeSessionPrincipal(
-                        OauthContextHolder.getId(jwt),
-                        OauthContextHolder.getBusiness(jwt),
-                        OauthContextHolder.getMobile(jwt),
-                        OauthContextHolder.getAuthority(jwt)
-                );
-                return this.timestampValidator.validate(jwt);
-            }
-        });
-
-        // 添加自定义令牌有效性验证器
+        validators.add(new JwtTimestampValidator());
         this.applicationContext.getBeansOfType(TokenActiveValidator.class).forEach((k, v) -> validators.add(v));
-        log.info("Loading token validity validators: {}", validators);
+        validators.add(jwt -> {
+            // 初始化会话主体信息
+            SessionContextHolder.initializeSessionPrincipal(
+                    OauthContextHolder.getId(jwt),
+                    OauthContextHolder.getScope(jwt),
+                    OauthContextHolder.getMobile(jwt),
+                    OauthContextHolder.getAuthority(jwt)
+            );
+            return OAuth2TokenValidatorResult.success();
+        });
+        log.info("Initialize token validators: {}", validators);
         decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
         return decoder;
     }
@@ -176,12 +168,6 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
         };
     }
 
-    @Bean
-    @ConditionalOnClass(RedisTemplate.class)
-    public TokenActiveValidator tokenActiveValidator(RedisTemplate<String, Object> redisTemplate) {
-        return new RedisTokenActiveValidator(redisTemplate);
-    }
-
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         // Http安全通用配置
@@ -197,12 +183,16 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
                         .authenticationEntryPoint(this.authenticationEntryPoint()).jwt().decoder(this.jwtDecoder())
                         .jwtAuthenticationConverter(this.jwtAuthenticationConverter()));
 
-        // 注销登录配置
+        // 退出登陆配置
+        LogoutConfigurer<HttpSecurity> logout = http.logout();
+        if (StringUtils.nonEmpty(this.properties.getResource().getLogout())) {
+            logout.logoutUrl(this.properties.getResource().getLogout());
+        }
         if (this.logoutHandler != null) {
-            http.logout().addLogoutHandler(this.logoutHandler);
+            logout.addLogoutHandler(this.logoutHandler);
         }
         if (this.logoutSuccessHandler != null) {
-            http.logout().logoutSuccessHandler(this.logoutSuccessHandler);
+            logout.logoutSuccessHandler(this.logoutSuccessHandler);
         }
 
         // 拒绝资源访问配置
@@ -257,7 +247,7 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
         Collection<AuthenticationProvider> providers =
                 this.applicationContext.getBeansOfType(AuthenticationProvider.class).values();
         if (!CollectionUtils.isEmpty(providers)) {
-            log.info("Loading authentication providers: {}", providers);
+            log.info("Initialize authentication providers: {}", providers);
             providers.forEach(auth::authenticationProvider);
         }
     }

+ 5 - 0
framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthProperties.java

@@ -46,6 +46,11 @@ public class OauthProperties {
          * 令牌签名密钥
          */
         private String secret;
+
+        /**
+         * 是否开启令牌复核
+         */
+        private boolean recheck;
     }
 
     /**

+ 47 - 154
framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java

@@ -1,40 +1,36 @@
 package com.chelvc.framework.oauth.context;
 
-import java.io.Serializable;
-import java.time.Duration;
 import java.util.List;
-import java.util.Map;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
 
-import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.model.Terminal;
-import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.redis.util.RedisUtils;
-import com.google.common.collect.Maps;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 
 /**
- * Oauth上下文工具类
+ * OAuth上下文工具类
  *
  * @author Woody
  * @date 2023/4/5
  */
 @Slf4j
-public class OauthContextHolder {
+@Component
+public class OauthContextHolder implements ServletRequestListener {
     /**
-     * 主体身份标识
+     * JWT身份标识
      */
-    public static final String JWT_CLAIM_ID = "id";
+    public static final String JWT_CLAIM_JTI = "jti";
 
     /**
-     * JWT身份标识
+     * 应用范围标识
      */
-    public static final String JWT_CLAIM_JTI = "jti";
+    public static final String JWT_CLAIM_SCOPE = "scope";
 
     /**
      * JWT手机号标识
@@ -42,46 +38,51 @@ public class OauthContextHolder {
     public static final String JWT_CLAIM_MOBILE = "mobile";
 
     /**
-     * 业务类型标识
+     * 用户账号标识
      */
-    public static final String JWT_CLAIM_BUSINESS = "business";
+    public static final String JWT_CLAIM_USER_NAME = "user_name";
 
     /**
-     * 用户账号标识
+     * 用户权限标识
      */
-    public static final String JWT_CLAIM_USER_NAME = "user_name";
+    public static final String JWT_CLAIM_AUTHORITIES = "authorities";
 
     /**
-     * 客户端账号标识
+     * 刷新令牌上下文
      */
-    public static final String JWT_CLAIM_CLIENT_ID = "client_id";
+    private static final ThreadLocal<OAuth2RefreshToken> REFRESH_TOKEN_CONTEXT = new ThreadLocal<>();
+
+    @Override
+    public void requestDestroyed(ServletRequestEvent event) {
+        clearOauthContext();
+    }
 
     /**
-     * 用户权限标识
+     * 获取当前刷新令牌
+     *
+     * @return 刷新令牌实例
      */
-    public static final String JWT_CLAIM_AUTHORITIES = "authorities";
+    public static OAuth2RefreshToken getRefreshToken() {
+        return REFRESH_TOKEN_CONTEXT.get();
+    }
 
     /**
-     * 获取主体身份
+     * 设置当前刷新令牌
      *
-     * @param jwt JWT对象
-     * @return 主体身份标识
+     * @param refreshToken 刷新令牌实例
      */
-    public static Long getId(Jwt jwt) {
-        return ObjectUtils.ifNull(jwt, t -> ObjectUtils.object2long(t.getClaim(JWT_CLAIM_ID)));
+    public static void setRefreshToken(OAuth2RefreshToken refreshToken) {
+        REFRESH_TOKEN_CONTEXT.set(refreshToken);
     }
 
     /**
      * 获取主体身份
      *
-     * @param token OAuth2访问令牌对象
+     * @param jwt JWT对象
      * @return 主体身份标识
      */
-    public static Long getId(OAuth2AccessToken token) {
-        return ObjectUtils.ifNull(
-                ObjectUtils.ifNull(token, OAuth2AccessToken::getAdditionalInformation),
-                info -> ObjectUtils.object2long(info.get(JWT_CLAIM_ID))
-        );
+    public static Long getId(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_USER_NAME), Long::parseLong);
     }
 
     /**
@@ -95,13 +96,13 @@ public class OauthContextHolder {
     }
 
     /**
-     * 获取用户标识
+     * 获取应用范围
      *
      * @param jwt JWT对象
-     * @return 用户标识
+     * @return 应用范围
      */
-    public static String getUser(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_USER_NAME), (String) null);
+    public static String getScope(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_SCOPE), (String) null);
     }
 
     /**
@@ -114,63 +115,6 @@ public class OauthContextHolder {
         return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_MOBILE), (String) null);
     }
 
-    /**
-     * 获取手机号
-     *
-     * @param token OAuth2访问令牌对象
-     * @return 手机号
-     */
-    public static String getMobile(OAuth2AccessToken token) {
-        return ObjectUtils.ifNull(
-                ObjectUtils.ifNull(token, OAuth2AccessToken::getAdditionalInformation),
-                info -> StringUtils.ifEmpty((String) info.get(JWT_CLAIM_MOBILE), (String) null)
-        );
-    }
-
-    /**
-     * 获取业务类型
-     *
-     * @param jwt JWT对象
-     * @return 业务类型
-     */
-    public static String getBusiness(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_BUSINESS), (String) null);
-    }
-
-    /**
-     * 获取业务类型
-     *
-     * @param id            会话ID
-     * @param redisTemplate Redis操作模版实例
-     * @return 业务类型
-     */
-    public static String getBusiness(@NonNull Serializable id, @NonNull RedisTemplate<String, Object> redisTemplate) {
-        return (String) redisTemplate.opsForHash().get(buildAccessTokenKey(id), "business");
-    }
-
-    /**
-     * 获取业务类型
-     *
-     * @param token OAuth2访问令牌对象
-     * @return 业务类型
-     */
-    public static String getBusiness(OAuth2AccessToken token) {
-        return ObjectUtils.ifNull(
-                ObjectUtils.ifNull(token, OAuth2AccessToken::getAdditionalInformation),
-                info -> StringUtils.ifEmpty((String) info.get(JWT_CLAIM_BUSINESS), (String) null)
-        );
-    }
-
-    /**
-     * 获取客户端标识
-     *
-     * @param jwt JWT对象
-     * @return 客户端标识
-     */
-    public static String getClient(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_CLIENT_ID), (String) null);
-    }
-
     /**
      * 获取用户授权信息
      *
@@ -189,71 +133,20 @@ public class OauthContextHolder {
     }
 
     /**
-     * 构建令牌标识
+     * 构建访问令牌标识
      *
-     * @param id 会话ID
+     * @param principal 主体标识
+     * @param terminal  终端信息
      * @return 令牌标识
      */
-    public static String buildAccessTokenKey(@NonNull Serializable id) {
-        return buildAccessTokenKey(id, SessionContextHolder.getTerminal());
+    public static String buildAccessTokenKey(@NonNull Object principal, @NonNull Terminal terminal) {
+        return String.format("token:%s:%s", terminal, principal);
     }
 
     /**
-     * 构建令牌标识
-     *
-     * @param id       会话ID
-     * @param terminal 终端信息
-     * @return 令牌标识
-     */
-    public static String buildAccessTokenKey(@NonNull Serializable id, @NonNull Terminal terminal) {
-        return String.format("token:%s:%s", id, terminal);
-    }
-
-    /**
-     * 存储访问令牌
-     *
-     * @param token         token OAuth2访问令牌对象
-     * @param redisTemplate Redis操作模版实例
-     */
-    public static void storeAccessToken(@NonNull OAuth2AccessToken token,
-                                        @NonNull RedisTemplate<String, Object> redisTemplate) {
-        String key = buildAccessTokenKey(getId(token));
-        if (log.isDebugEnabled()) {
-            log.debug("Saving redis token: {}", key);
-        }
-        Map<String, Object> context = Maps.newHashMapWithExpectedSize(2);
-        context.put("value", token.getValue());
-        String business = getBusiness(token);
-        if (StringUtils.nonEmpty(business)) {
-            context.put("business", business);
-        }
-        long millis = Math.max(token.getExpiration().getTime() - System.currentTimeMillis(), 0);
-        RedisUtils.hmset(redisTemplate, key, context, Duration.ofMillis(millis));
-    }
-
-    /**
-     * 删除访问令牌
-     *
-     * @param token         token OAuth2访问令牌对象
-     * @param redisTemplate Redis操作模版实例
-     */
-    public static void removeAccessToken(@NonNull OAuth2AccessToken token,
-                                         @NonNull RedisTemplate<String, Object> redisTemplate) {
-        String key = buildAccessTokenKey(getId(token));
-        if (log.isDebugEnabled()) {
-            log.debug("Saving redis token: {}", key);
-        }
-        redisTemplate.delete(key);
-    }
-
-    /**
-     * 获取访问令牌
-     *
-     * @param jwt           JWT实例
-     * @param redisTemplate Redis操作模版实例
-     * @return 访问令牌
+     * 清空OAuth上下文
      */
-    public static String getAccessToken(@NonNull Jwt jwt, @NonNull RedisTemplate<String, Object> redisTemplate) {
-        return (String) redisTemplate.opsForHash().get(buildAccessTokenKey(getId(jwt)), "value");
+    public static void clearOauthContext() {
+        REFRESH_TOKEN_CONTEXT.remove();
     }
 }

+ 13 - 6
framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenActiveValidator.java

@@ -2,11 +2,15 @@ package com.chelvc.framework.oauth.token;
 
 import java.util.Objects;
 
+import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.oauth.config.OauthConfigurer;
 import com.chelvc.framework.oauth.context.OauthContextHolder;
 import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -14,6 +18,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.server.resource.BearerTokenError;
+import org.springframework.stereotype.Component;
 
 /**
  * 基于Redis的令牌有效性验证实现
@@ -24,19 +29,21 @@ import org.springframework.security.oauth2.server.resource.BearerTokenError;
  * @see TokenActiveValidator
  */
 @Slf4j
+@Component
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+@ConditionalOnProperty(prefix = "platform.oauth.token", name = "recheck", havingValue = "true")
 public class RedisTokenActiveValidator implements TokenActiveValidator {
     private final RedisTemplate<String, Object> redisTemplate;
 
-    public RedisTokenActiveValidator(@NonNull RedisTemplate<String, Object> redisTemplate) {
-        this.redisTemplate = redisTemplate;
-    }
-
     @Override
     public OAuth2TokenValidatorResult validate(@NonNull Jwt jwt) {
         // 验证Redis中是否存在该令牌,同时忽略Redis请求/操作异常(为了提高系统可用性)
         String token = null;
+        String key = OauthContextHolder.buildAccessTokenKey(
+                OauthContextHolder.getId(jwt), SessionContextHolder.getTerminal()
+        );
         try {
-            if (StringUtils.isEmpty(token = OauthContextHolder.getAccessToken(jwt, this.redisTemplate))) {
+            if (StringUtils.isEmpty(token = (String) this.redisTemplate.opsForValue().get(key))) {
                 return OAuth2TokenValidatorResult.failure(
                         new OAuth2Error("invalid_token", "Token is expired", null)
                 );
@@ -48,7 +55,7 @@ public class RedisTokenActiveValidator implements TokenActiveValidator {
         // 判断令牌是否相同,如果不相同则标识令牌已经被重置(账号已在其他地方登录)
         if (StringUtils.nonEmpty(token) && !Objects.equals(token, jwt.getTokenValue())) {
             throw new OAuth2AuthenticationException(
-                    new BearerTokenError("invalid_token", HttpStatus.GONE, "Token is reset", null)
+                    new BearerTokenError("invalid_token", HttpStatus.GONE, "Token has been reset", null)
             );
         }
         return OAuth2TokenValidatorResult.success();

+ 20 - 4
framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenStore.java

@@ -1,9 +1,15 @@
 package com.chelvc.framework.oauth.token;
 
+import java.time.Duration;
+
+import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.oauth.context.OauthContextHolder;
+import com.chelvc.framework.redis.util.RedisUtils;
 import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
 import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@@ -14,6 +20,7 @@ import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
  * @author Woody
  * @date 2023/4/5
  */
+@Slf4j
 public class RedisTokenStore extends JwtTokenStore {
     private final RedisTemplate<String, Object> redisTemplate;
 
@@ -24,12 +31,21 @@ public class RedisTokenStore extends JwtTokenStore {
     }
 
     @Override
-    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
-        OauthContextHolder.storeAccessToken(token, this.redisTemplate);
+    public OAuth2RefreshToken readRefreshToken(String tokenValue) {
+        OAuth2RefreshToken refreshToken = super.readRefreshToken(tokenValue);
+        OauthContextHolder.setRefreshToken(refreshToken);
+        return refreshToken;
     }
 
     @Override
-    public void removeAccessToken(OAuth2AccessToken token) {
-        OauthContextHolder.removeAccessToken(token, this.redisTemplate);
+    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
+        String key = OauthContextHolder.buildAccessTokenKey(
+                authentication.getPrincipal(), SessionContextHolder.getTerminal()
+        );
+        if (log.isDebugEnabled()) {
+            log.debug("Saving redis token: {}", key);
+        }
+        Duration duration = RedisUtils.duration(token.getExpiration());
+        this.redisTemplate.opsForValue().set(key, token.getValue(), duration);
     }
 }

+ 1 - 1
framework-redis/src/main/java/com/chelvc/framework/redis/config/RedisConfigurer.java

@@ -65,7 +65,7 @@ public class RedisConfigurer extends CachingConfigurerSupport implements Applica
         Collection<RedisEventConfigurerAdapter> adapters =
                 applicationContext.getBeansOfType(RedisEventConfigurerAdapter.class).values();
         if (!CollectionUtils.isEmpty(adapters)) {
-            log.info("Loading redis event configurer adapters: {}", adapters);
+            log.info("Initialize redis event configurer adapters: {}", adapters);
             adapters.forEach(adapter -> adapter.configure(bus));
         }
     }

+ 2 - 2
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java

@@ -165,7 +165,7 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
                 .filter(handler -> handler instanceof RocketMQTransactionChecker)
                 .map(handler -> (RocketMQTransactionChecker) handler).collect(Collectors.toList());
         if (!CollectionUtils.isEmpty(checkers)) {
-            log.info("Loading rocketmq transaction checkers: {}", checkers);
+            log.info("Initialize rocketmq transaction checkers: {}", checkers);
             MESSAGE_CHECKER_MAPPING = Maps.newHashMapWithExpectedSize(checkers.size());
             checkers.forEach(checker -> {
                 Class<?> clazz = checker.getPayloadClass();
@@ -182,7 +182,7 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
                 .filter(handler -> handler instanceof RocketMQTransactionExecutor)
                 .map(handler -> (RocketMQTransactionExecutor) handler).collect(Collectors.toList());
         if (!CollectionUtils.isEmpty(executors)) {
-            log.info("Loading rocketmq transaction executors: {}", executors);
+            log.info("Initialize rocketmq transaction executors: {}", executors);
             MESSAGE_EXECUTOR_MAPPING = Maps.newHashMapWithExpectedSize(executors.size());
             executors.forEach(executor -> {
                 Class<?> clazz = executor.getPayloadClass();