From e73c058fcc8c9123a04d3f0e005cc820bf6e8b39 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Sun, 4 Oct 2026 20:22:35 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(auth):=20=E7=99=BB=E5=BD=95=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=88=87=E6=8D=A2=E8=87=B3=20Starter=20Refresh=20Toke?= =?UTF-8?q?n=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入 continew-starter-auth-refresh-token(ContiNew Starter 2.17.0),删除 continew-auth-refresh 模块,登录会话能力改由 Starter 模块统一供给: - 父 POM 升级至 ContiNew Starter 2.17.0-SNAPSHOT,Enforcer/Spotless/Checkstyle/SpotBugs 门禁插件与版本改由父 POM 统一供给,移除本地声明 - continew-common 新增 continew-starter-auth-refresh-token 依赖(排除 spring-boot-starter-web, 与既有认证依赖保持一致) - 删除 continew-auth-refresh 模块:可轮换会话核心实现已上移 Starter,剩余 Web 层按归属 就位——SessionController、RefreshTokenReq、LoginResp 移入 continew-system 对应包(包名 不变,同步消除 model.resp 跨 jar split package);RefreshTokenExceptionHandler 移入 continew-common 全局异常处理包;刷新端点改用 Starter refresh 模板固化四步安全契约 - 新增 RefreshSessionPrincipalAdapter 适配 Starter 登录主体快照 SPI; RefreshAccessTokenIssuerImpl 适配 Starter IssuedAccessToken 签发契约 - 配置前缀 auth.refresh-token 迁移至 continew-starter.refresh-token;dev 显式声明 cookie-insecure-allowed 确认本地 HTTP 的知情选择 - 适配 Starter 工具类更名:URLUtils→UrlUtils、JSONUtils→JsonUtils、 ServletUtils.writeJSON→writeJson、ContiNewStarterVersion.getVersion→VERSION - 登出 Cookie 安全边界测试(SessionControllerTest)随迁至 continew-system 并在 CI 实际运行 Assisted-by: ZCode --- continew-auth-refresh/pom.xml | 46 - .../auth/api/AccessSessionValidator.java | 39 - .../admin/auth/api/AuthPolicyLockTarget.java | 43 - .../api/AuthPolicyLockTargetResolver.java | 31 - .../admin/auth/api/AuthPolicyWriteLocked.java | 38 - .../admin/auth/api/AuthSessionConstants.java | 34 - .../api/AuthSessionRevocationNotifier.java | 32 - .../auth/config/RefreshTokenProperties.java | 211 ----- .../admin/auth/enums/LogoutReasonEnum.java | 49 -- .../auth/enums/RefreshTokenModeEnum.java | 43 - .../auth/enums/SessionReplacementScope.java | 31 - .../auth/exception/RefreshTokenException.java | 61 -- .../admin/auth/model/AuthSecurityVersion.java | 32 - .../admin/auth/model/RefreshClientPolicy.java | 35 - .../auth/model/RefreshRotationResult.java | 50 -- .../admin/auth/model/RefreshSession.java | 102 --- .../admin/auth/model/SessionView.java | 45 - .../service/RefreshAccessTokenIssuer.java | 36 - .../auth/service/RefreshTokenService.java | 134 --- .../service/SessionInvalidationService.java | 29 - .../auth/service/SessionQueryService.java | 29 - .../service/impl/RefreshTokenServiceImpl.java | 808 ------------------ .../impl/SessionInvalidationServiceImpl.java | 73 -- .../service/impl/SessionQueryServiceImpl.java | 46 - .../auth/support/AccessSessionCache.java | 118 --- .../admin/auth/support/AuthPolicyLock.java | 63 -- .../support/AuthPolicyWriteLockAspect.java | 119 --- .../auth/support/RefreshSessionStore.java | 363 -------- .../admin/auth/support/RefreshTokenCodec.java | 196 ----- .../support/RefreshTokenRequestGuard.java | 303 ------- ...enantArgumentPolicyLockTargetResolver.java | 46 - .../UserArgumentPolicyLockTargetResolver.java | 46 - .../AuthWebSocketSessionService.java | 210 ----- .../ConcurrentWebSocketSessionDao.java | 96 --- .../websocket/MultiWebSocketSessionDao.java | 49 -- .../websocket/WebSocketClientServiceImpl.java | 50 -- .../WebSocketSessionDaoConfiguration.java | 39 - .../service/RefreshTokenServiceImplTest.java | 564 ------------ .../auth/support/AccessSessionCacheTest.java | 116 --- .../auth/support/RefreshTokenCodecTest.java | 92 -- ...rArgumentPolicyLockTargetResolverTest.java | 55 -- .../AuthWebSocketSessionServiceTest.java | 256 ------ continew-common/pom.xml | 12 + .../RefreshTokenExceptionHandler.java | 4 +- .../continew-plugin-tenant/pom.xml | 5 - ...PackageTenantPolicyLockTargetResolver.java | 4 +- .../service/impl/PackageServiceImpl.java | 2 +- .../service/impl/TenantServiceImpl.java | 4 +- continew-server/pom.xml | 5 - .../admin/ContiNewAdminApplication.java | 4 +- .../satoken/AccessTokenSecretValidator.java | 3 +- .../satoken/SaExtensionInterceptor.java | 8 +- .../config/satoken/SaTokenConfiguration.java | 2 +- .../main/resources/config/application-dev.yml | 20 +- .../resources/config/application-prod.yml | 28 +- .../src/main/resources/config/application.yml | 60 +- .../service/OnlineUserServiceImplTest.java | 24 +- .../satoken/SaExtensionInterceptorTest.java | 2 +- continew-system/pom.xml | 24 +- .../ClientPolicyLockTargetResolver.java | 4 +- .../DeptUserPolicyLockTargetResolver.java | 4 +- .../adapter/RefreshAccessTokenIssuerImpl.java | 15 +- .../adapter/RefreshClientPolicyMapper.java | 6 +- .../RefreshSessionPrincipalAdapter.java | 74 ++ .../auth/controller/OnlineUserController.java | 8 +- .../auth/controller/SessionController.java | 21 +- .../admin/auth/model/req/RefreshTokenReq.java | 0 .../admin/auth/model/resp/LoginResp.java | 0 .../admin/auth/service/AuthTokenService.java | 2 +- .../service/impl/AuthTokenServiceImpl.java | 31 +- .../service/impl/OnlineUserServiceImpl.java | 6 +- .../api/TenantDataApiForSystemImpl.java | 2 +- .../system/config/file/FileRecorderImpl.java | 8 +- .../system/config/sms/SmsConfigUtil.java | 4 +- .../system/controller/MenuController.java | 6 +- .../admin/system/enums/StorageTypeEnum.java | 4 +- .../admin/system/model/entity/ClientDO.java | 2 +- .../admin/system/model/req/ClientReq.java | 6 +- .../admin/system/model/resp/ClientResp.java | 2 +- .../service/impl/ClientServiceImpl.java | 7 +- .../system/service/impl/DeptServiceImpl.java | 2 +- .../system/service/impl/UserServiceImpl.java | 4 +- .../controller/SessionControllerTest.java | 6 +- pom.xml | 20 +- 84 files changed, 268 insertions(+), 5045 deletions(-) delete mode 100644 continew-auth-refresh/pom.xml delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AccessSessionValidator.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTarget.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTargetResolver.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyWriteLocked.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionConstants.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionRevocationNotifier.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/config/RefreshTokenProperties.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/LogoutReasonEnum.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/RefreshTokenModeEnum.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/SessionReplacementScope.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/exception/RefreshTokenException.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/model/AuthSecurityVersion.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshClientPolicy.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshRotationResult.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshSession.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/model/SessionView.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshAccessTokenIssuer.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshTokenService.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionInvalidationService.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionQueryService.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/RefreshTokenServiceImpl.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionInvalidationServiceImpl.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionQueryServiceImpl.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AccessSessionCache.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyLock.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyWriteLockAspect.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshSessionStore.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenCodec.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenRequestGuard.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/TenantArgumentPolicyLockTargetResolver.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolver.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/AuthWebSocketSessionService.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/ConcurrentWebSocketSessionDao.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/MultiWebSocketSessionDao.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketClientServiceImpl.java delete mode 100644 continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketSessionDaoConfiguration.java delete mode 100644 continew-auth-refresh/src/test/java/top/continew/admin/auth/service/RefreshTokenServiceImplTest.java delete mode 100644 continew-auth-refresh/src/test/java/top/continew/admin/auth/support/AccessSessionCacheTest.java delete mode 100644 continew-auth-refresh/src/test/java/top/continew/admin/auth/support/RefreshTokenCodecTest.java delete mode 100644 continew-auth-refresh/src/test/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolverTest.java delete mode 100644 continew-auth-refresh/src/test/java/top/continew/admin/auth/websocket/AuthWebSocketSessionServiceTest.java rename {continew-auth-refresh/src/main/java/top/continew/admin/auth/web => continew-common/src/main/java/top/continew/admin/common/config/exception}/RefreshTokenExceptionHandler.java (93%) create mode 100644 continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshSessionPrincipalAdapter.java rename {continew-auth-refresh => continew-system}/src/main/java/top/continew/admin/auth/controller/SessionController.java (80%) rename {continew-auth-refresh => continew-system}/src/main/java/top/continew/admin/auth/model/req/RefreshTokenReq.java (100%) rename {continew-auth-refresh => continew-system}/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java (100%) rename {continew-auth-refresh => continew-system}/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java (94%) diff --git a/continew-auth-refresh/pom.xml b/continew-auth-refresh/pom.xml deleted file mode 100644 index 24b7b44da..000000000 --- a/continew-auth-refresh/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 4.0.0 - - top.continew.admin - continew-admin - ${revision} - - - continew-auth-refresh - jar - - ${project.artifactId} - 认证会话模块(Refresh Token、会话轮换和 Access Token 绑定) - - - - ${project.groupId} - continew-common - - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - - - - diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AccessSessionValidator.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AccessSessionValidator.java deleted file mode 100644 index 80e36b620..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AccessSessionValidator.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -/** 校验 Access Token 是否仍绑定有效认证会话。 */ -public interface AccessSessionValidator { - - /** - * 会话失效原因提示。 - * - * @param accessToken Access Token - * @return 失效提示;null 表示会话仍然有效 - */ - String getInvalidReason(String accessToken); - - /** - * 判断 Access Token 绑定的认证会话是否已经失效。 - * - * @param accessToken Access Token - * @return 已失效返回 true - */ - default boolean isInvalid(String accessToken) { - return this.getInvalidReason(accessToken) != null; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTarget.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTarget.java deleted file mode 100644 index fa0afbd6b..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTarget.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -/** - * 认证会话安全策略锁的目标。 - * - * @author luoqiz - */ -public record AuthPolicyLockTarget(Type type, String key) { - - public static AuthPolicyLockTarget client(String clientId) { - return new AuthPolicyLockTarget(Type.CLIENT, clientId); - } - - public static AuthPolicyLockTarget tenant(Long tenantId) { - return new AuthPolicyLockTarget(Type.TENANT, String.valueOf(tenantId)); - } - - public static AuthPolicyLockTarget user(Long userId) { - return new AuthPolicyLockTarget(Type.USER, String.valueOf(userId)); - } - - public enum Type { - USER, - TENANT, - CLIENT - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTargetResolver.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTargetResolver.java deleted file mode 100644 index 7b97f0e51..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyLockTargetResolver.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -import java.util.Collection; - -/** - * 将业务方法参数解析为认证会话策略锁目标。 - * - *

实现由 system 或 tenant 插件提供,认证会话模块不引用任何业务 Mapper。

- * - * @author luoqiz - */ -public interface AuthPolicyLockTargetResolver { - - Collection resolve(Object[] args); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyWriteLocked.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyWriteLocked.java deleted file mode 100644 index fd38112bd..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthPolicyWriteLocked.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 在数据库事务开始前获取认证策略写锁。 - * - *

标记会改变登录资格或会话策略的项目业务方法,统一约束分布式锁与数据库事务的 - * 顺序,避免登录/刷新路径的“Redis 锁→数据库”与管理路径的“数据库→Redis 锁”互锁。

- * - * @author luoqiz - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface AuthPolicyWriteLocked { - - /** 由业务模块实现的锁目标解析器。 */ - Class value(); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionConstants.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionConstants.java deleted file mode 100644 index b825478eb..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionConstants.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -/** - * 认证会话常量。 - * - * @author luoqiz - */ -public final class AuthSessionConstants { - - /** Access Token 中绑定 Refresh Session ID 的声明名称。 */ - public static final String SESSION_ID_CLAIM = "sid"; - - /** 会话失效广播 Topic 前缀,完整 Topic 默认追加应用名:{prefix}:{spring.application.name} */ - public static final String ACCESS_SESSION_INVALID_TOPIC_PREFIX = "auth:access-session-invalid"; - - private AuthSessionConstants() { - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionRevocationNotifier.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionRevocationNotifier.java deleted file mode 100644 index 545a02375..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/api/AuthSessionRevocationNotifier.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.api; - -/** - * 认证会话撤销通知器。 - * - * @author luoqiz - */ -public interface AuthSessionRevocationNotifier { - - /** - * 通知所有实时连接撤销指定登录会话。 - * - * @param sessionId Refresh Session ID - */ - void notifyRevoked(String sessionId); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/config/RefreshTokenProperties.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/config/RefreshTokenProperties.java deleted file mode 100644 index a30e78fde..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/config/RefreshTokenProperties.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.config; - -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -/** - * Refresh Token 配置。 - * - * @author luoqiz - * @since 4.2.0 - */ -@Data -@Component -@Validated -@ConfigurationProperties(prefix = "auth.refresh-token") -public class RefreshTokenProperties { - - private static final String DNS_LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?"; - private static final java.util.regex.Pattern WILDCARD_COOKIE_ORIGIN = - java.util.regex.Pattern.compile( - "^https?://\\*\\.(" + DNS_LABEL + "(?:\\." + DNS_LABEL + ")+)$", - java.util.regex.Pattern.CASE_INSENSITIVE); - - /** - * Refresh Token 服务端密钥。 - * - *

用于派生 Token 指纹密钥和轮换快照加密密钥。生产环境必须通过 - * {@code REFRESH_TOKEN_SECRET} 单独配置高熵随机值。

- */ - @NotBlank(message = "Refresh Token 服务端密钥不能为空") - @Size(min = 32, message = "Refresh Token 服务端密钥长度不能少于 32 个字符") - private String secret; - - /** 浏览器 Refresh Token Cookie 名称 */ - @NotBlank(message = "Refresh Token Cookie 名称不能为空") - private String cookieName = "refresh_token"; - - /** - * Cookie 作用路径。 - * - *

前端开发环境通常通过 /api 或 /dev-api 代理访问后端,浏览器判断 Cookie - * Path 时使用的是代理后的前端 URL,因此不能设置为 /auth,否则刷新请求不会携带 - * Cookie。生产环境如使用固定网关前缀,可通过配置覆盖该值。

- */ - @NotBlank(message = "Refresh Token Cookie Path 不能为空") - private String cookiePath = "/"; - - /** 生产环境必须开启 Secure;开发环境可关闭以支持 HTTP 本地调试 */ - private boolean cookieSecure; - - /** Cookie SameSite 属性 */ - @Pattern(regexp = "(?i)Strict|Lax|None", message = "Cookie SameSite 只能是 Strict、Lax 或 None") - private String cookieSameSite = "Lax"; - - /** - * 允许携带 Refresh Token Cookie 的跨源前端 Origin。 - * - *

空列表表示只允许请求自身同源。每一项可以是无路径、查询和片段的明确 HTTP(S) - * Origin,或格式为 {@code http[s]://*.example.com} 的子域通配符。通配符仅匹配最左侧 - * 的一个 DNS 标签,例如 {@code http://*.luoqiz.top} 可匹配 - * {@code http://admin.luoqiz.top}。

- */ - private List cookieAllowedOrigins = new ArrayList<>(); - - /** SameSite=None 只有在 HTTPS 下配合 Secure 才能被浏览器接受。 */ - @AssertTrue(message = "Cookie SameSite=None 时必须同时开启 Secure") - public boolean isCookieSecurityValid() { - return !"None".equalsIgnoreCase(cookieSameSite) || cookieSecure; - } - - /** __Host- Cookie 必须满足浏览器规定的 Secure + Path=/ 约束。 */ - @AssertTrue(message = "__Host- Refresh Token Cookie 必须开启 Secure 且 Path=/") - public boolean isHostCookieValid() { - return !cookieName.startsWith("__Host-") || cookieSecure && "/".equals(cookiePath); - } - - /** Cookie 来源白名单必须是严格 HTTP(S) Origin 或受限的子域通配符。 */ - @AssertTrue( - message = "Refresh Token Cookie 允许来源必须是明确的 HTTP(S) Origin 或 http[s]://*.example.com 格式的子域通配符") - public boolean isCookieAllowedOriginsValid() { - return cookieAllowedOrigins != null && cookieAllowedOrigins.stream() - .allMatch(this::isValidAllowedOrigin); - } - - /** 同一个旧 Token 的并发请求可重放第一次轮换结果的时间(秒)。 */ - @Min(value = 1, message = "Refresh Token 轮换宽限期不能少于 1 秒") - @Max(value = 30, message = "Refresh Token 轮换宽限期不能超过 30 秒") - private int rotationGracePeriod = 5; - - /** 刷新接口单个 IP 在限流窗口内允许的最大请求数,0 表示交由网关限流。 */ - @Min(value = 0, message = "Refresh Token IP 限流次数不能小于 0") - @Max(value = 10000, message = "Refresh Token IP 限流次数不能超过 10000") - private int ipRateLimit = 60; - - /** Refresh Token IP 限流窗口(秒)。 */ - @Min(value = 1, message = "Refresh Token IP 限流窗口不能少于 1 秒") - @Max(value = 3600, message = "Refresh Token IP 限流窗口不能超过 3600 秒") - private int ipRateLimitPeriod = 60; - - /** 单个登录会话在限流窗口内允许的最大刷新次数。 */ - @Min(value = 1, message = "Refresh Token 会话限流次数不能少于 1") - @Max(value = 1000, message = "Refresh Token 会话限流次数不能超过 1000") - private int sessionRateLimit = 10; - - /** 单个登录会话刷新限流窗口(秒)。 */ - @Min(value = 1, message = "Refresh Token 会话限流窗口不能少于 1 秒") - @Max(value = 3600, message = "Refresh Token 会话限流窗口不能超过 3600 秒") - private int sessionRateLimitPeriod = 60; - - /** 允许解析转发地址的反向代理地址列表;空列表时始终使用连接对端地址。 */ - private List trustedProxyAddresses = new ArrayList<>(); - - /** 可信代理追加到 X-Forwarded-For 的跳数;0 表示不解析转发地址。 */ - @Min(value = 0, message = "可信代理跳数不能小于 0") - @Max(value = 10, message = "可信代理跳数不能超过 10") - private int trustedProxyHops; - - /** - * 热路径会话校验本地缓存开关。 - * - *

开启后,每个已登录请求的会话校验命中本地缓存为 0 次 Redis 往返;会话撤销经 - * 广播子秒级失效,最坏情况由本地缓存 TTL(2 秒)兜底。关闭则恢复逐请求实时校验 - * (每次 2 次 Redis 往返,撤销立即生效)。

- */ - private boolean accessSessionCacheEnabled = true; - - /** - * 会话失效广播 Topic 名称(显式覆盖)。 - * - *

留空(默认)时使用 {@code auth:access-session-invalid:{spring.application.name}}: - * 同一服务多副本共用 Topic、不同服务天然隔离。仅当多个服务有意共享同一认证会话域 - * (例如网关与资源服务拆分、共用同一套 Refresh Session)时才显式配置为公共 Topic。 - * 必须与 jetcache 的 broadcastChannel 取值不同,二者消息载荷不兼容。

- */ - private String accessSessionInvalidTopic = ""; - - /** - * 本实例 WebSocket 连接凭证周期校验间隔(毫秒)。 - * - *

兜底 Redis Pub/Sub 撤销消息丢失和 Access Token 自然过期;默认 60 秒, - * 与撤销广播配合时,未能及时收到通知的连接最迟在该间隔内被关闭。

- */ - @Min(value = 1000, message = "WebSocket 连接校验间隔不能少于 1000 毫秒") - @Max(value = 3600000, message = "WebSocket 连接校验间隔不能超过 3600000 毫秒") - private long websocketValidationInterval = 60000; - - private boolean isValidAllowedOrigin(String value) { - if (value == null || value.isBlank() || !value.equals(value.trim()) - || "*".equals(value)) { - return false; - } - return this.isValidOrigin(value) || WILDCARD_COOKIE_ORIGIN.matcher(value).matches(); - } - - /** - * 校验字符串是否为严格的无路径/查询/片段的 HTTP(S) Origin。 - * - *

请求守卫的 {@code parseOrigin} 与本方法共用同一份语义,避免两份校验漂移: - * 端口只允许省略(-1)或 1-65535(拒绝 0),并拒绝 {@code https://host:} 这类 - * 以冒号结尾的空端口写法。

- * - * @param value 待校验的 Origin - * @return true:合法 HTTP(S) Origin;false:非法 - */ - public static boolean isValidOrigin(String value) { - try { - URI uri = URI.create(value); - String scheme = uri.getScheme(); - int port = uri.getPort(); - return ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) - && uri.getHost() != null - && uri.getUserInfo() == null - && (uri.getRawPath() == null || uri.getRawPath().isEmpty()) - && uri.getRawQuery() == null - && uri.getRawFragment() == null - && (port == -1 || port > 0 && port <= 65535) - && !uri.getRawAuthority().endsWith(":"); - } catch (IllegalArgumentException e) { - return false; - } - } - -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/LogoutReasonEnum.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/LogoutReasonEnum.java deleted file mode 100644 index b3dcc7eb3..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/LogoutReasonEnum.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.enums; - -/** - * 登录会话失效原因。 - * - *

Sa-Token 不再管理并发登录与顶人下线,客户端配置的注销模式只能由 Refresh Session - * 在撤销会话时落到这里,才能把“被踢下线”“被顶下线”“已注销”区分给客户端。原因只用于 - * 改善提示文案:标记过期后请求会回落到 {@link #LOGOUT} 的默认提示。

- * - * @author luoqiz - */ -public enum LogoutReasonEnum { - - /** 主动注销或会话自然失效。 */ - LOGOUT("登录状态已失效,请重新登录"), - - /** 被管理员强退,或因用户、租户、客户端变更被强制下线。 */ - KICKOUT("您已被管理员强制下线,请重新登录"), - - /** 登录数量超限或不允许多地登录,被新登录顶下线。 */ - REPLACED("您的账号已在其他设备登录,请重新登录"); - - private final String message; - - LogoutReasonEnum(String message) { - this.message = message; - } - - /** 返回给客户端的失效提示。 */ - public String getMessage() { - return this.message; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/RefreshTokenModeEnum.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/RefreshTokenModeEnum.java deleted file mode 100644 index dbe3b160a..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/RefreshTokenModeEnum.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import top.continew.starter.core.enums.BaseEnum; - -/** - * Refresh Token 传输模式。 - * - *

COOKIE 适用于浏览器:长期凭证由 HttpOnly Cookie 承载,前端 JavaScript 无法读取。 - * BODY 适用于 App、小程序,长期凭证由客户端安全存储后通过请求体提交。

- * - * @author luoqiz - */ -@Getter -@RequiredArgsConstructor -public enum RefreshTokenModeEnum implements BaseEnum { - - /** 浏览器 Cookie 模式 */ - COOKIE("COOKIE", "Cookie"), - - /** 原生 App / 小程序请求体模式 */ - BODY("BODY", "请求体"); - - private final String value; - private final String description; -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/SessionReplacementScope.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/SessionReplacementScope.java deleted file mode 100644 index 7cdcfcb58..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/enums/SessionReplacementScope.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.enums; - -/** - * 非并发登录时替换既有登录会话的范围。 - * - * @author luoqiz - */ -public enum SessionReplacementScope { - - /** 仅替换相同客户端类型的会话。 */ - CURRENT_CLIENT_TYPE, - - /** 替换该用户的所有客户端类型会话。 */ - ALL_CLIENT_TYPES -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/exception/RefreshTokenException.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/exception/RefreshTokenException.java deleted file mode 100644 index 847965efd..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/exception/RefreshTokenException.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.exception; - -import lombok.Getter; -import org.springframework.http.HttpStatus; -import top.continew.starter.core.exception.BusinessException; - -import java.io.Serial; - -/** - * Refresh Token 协议异常。 - * - *

认证终止、来源拒绝和限流必须使用稳定且可区分的业务状态码,客户端才能只在 - * Refresh Token 确定失效时清理登录态。

- * - * @author luoqiz - */ -@Getter -public class RefreshTokenException extends BusinessException { - - @Serial - private static final long serialVersionUID = 1L; - - private final HttpStatus status; - - private RefreshTokenException(HttpStatus status, String message) { - super(message); - this.status = status; - } - - public static RefreshTokenException unauthorized(String message) { - return new RefreshTokenException(HttpStatus.UNAUTHORIZED, message); - } - - public static RefreshTokenException forbidden(String message) { - return new RefreshTokenException(HttpStatus.FORBIDDEN, message); - } - - public static RefreshTokenException tooManyRequests(String message) { - return new RefreshTokenException(HttpStatus.TOO_MANY_REQUESTS, message); - } - - public static RefreshTokenException internalServerError(String message) { - return new RefreshTokenException(HttpStatus.INTERNAL_SERVER_ERROR, message); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/AuthSecurityVersion.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/AuthSecurityVersion.java deleted file mode 100644 index d7ef22d84..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/AuthSecurityVersion.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.model; - -/** - * 登录安全状态版本快照。 - * - *

快照在最终状态复查前读取,并随 Refresh Session 保存。用户、客户端或租户发生 - * 强制失效操作时递增对应版本,能够使并发创建但未被索引扫描命中的会话立即失效。

- * - * @param userVersion 用户安全版本 - * @param clientVersion 客户端安全版本 - * @param tenantVersion 租户安全版本 - * @author luoqiz - * @since 4.2.0 - */ -public record AuthSecurityVersion(long userVersion, long clientVersion, long tenantVersion) { -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshClientPolicy.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshClientPolicy.java deleted file mode 100644 index 722bd32fe..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshClientPolicy.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.model; - -import top.continew.admin.auth.enums.LogoutReasonEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; -import top.continew.admin.auth.enums.SessionReplacementScope; - -/** - * 认证会话模块所需的客户端令牌策略。 - * - *

该模型刻意不复用系统管理模块的 {@code ClientResp},避免认证会话模块反向依赖 - * 用户、客户端等业务实体。

- * - * @author luoqiz - */ -public record RefreshClientPolicy(String clientId, String clientType, long refreshTokenTimeout, - RefreshTokenModeEnum refreshTokenMode, boolean concurrent, - SessionReplacementScope replacementScope, int maxLoginCount, - LogoutReasonEnum overflowLogoutMode) { -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshRotationResult.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshRotationResult.java deleted file mode 100644 index 8ff54cea5..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshRotationResult.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.model; - -import lombok.Data; -import lombok.NoArgsConstructor; -import java.io.Serial; -import java.io.Serializable; - -/** - * Refresh Token 短时轮换结果。 - * - *

Redis 中的 Access Token 和 Refresh Token 均为 AES-GCM 密文,记录只在并发宽限期内 - * 存活,用于让浏览器多标签页、App 和小程序弱网重试得到完全相同的结果。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Data -@NoArgsConstructor -public class RefreshRotationResult implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - private String encryptedAccessToken; - private String encryptedRefreshToken; - private Long expiresIn; - private Long refreshExpiresIn; - private Long tenantId; - - /** 是否已经完成 Access Token 签发,可以直接幂等返回。 */ - public boolean isComplete() { - return encryptedAccessToken != null; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshSession.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshSession.java deleted file mode 100644 index b1ccd9d18..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/RefreshSession.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.model; - -import lombok.Data; -import lombok.NoArgsConstructor; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; - -import java.io.Serial; -import java.io.Serializable; - -/** - * Redis 中保存的 Refresh Token 会话。 - * - *

一条记录对应一次登录会话。Refresh Token 使用 {@code sessionId.secret} 格式, - * Redis 只保存 secret 的指纹,轮换只需要原子更新本对象。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Data -@NoArgsConstructor -public class RefreshSession implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 登录会话 ID,同时是 Refresh Token 的公开定位部分。 */ - private String sessionId; - - /** 用户 ID */ - private Long userId; - - /** 登录时的用户名快照,用于登录会话管理。 */ - private String username; - - /** 登录时的用户昵称快照,用于登录会话管理。 */ - private String nickname; - - /** 客户端 ID */ - private String clientId; - - /** 客户端类型,用于执行同类型设备互斥登录策略。 */ - private String clientType; - - /** 登录时确定的租户 ID,避免刷新时跨租户使用 */ - private Long tenantId; - - /** 当前客户端使用的 Refresh Token 传输模式 */ - private RefreshTokenModeEnum mode; - - /** 登录会话创建时间(毫秒时间戳),用于最大登录数量的稳定淘汰顺序。 */ - private long createdAt; - - /** 最近一次成功发起令牌轮换的时间(毫秒时间戳)。 */ - private long lastRefreshAt; - - /** 初次登录 IP。 */ - private String ip; - - /** 初次登录 IP 归属地。 */ - private String address; - - /** 初次登录浏览器或客户端。 */ - private String browser; - - /** 初次登录操作系统。 */ - private String os; - - /** 整个登录会话的绝对过期时间(毫秒时间戳),轮换不会无限延长会话寿命 */ - private long expiresAt; - - /** 创建会话时的用户安全版本。 */ - private long userSecurityVersion; - - /** 创建会话时的客户端安全版本。 */ - private long clientSecurityVersion; - - /** 创建会话时的租户安全版本。 */ - private long tenantSecurityVersion; - - /** 当前 Refresh Token secret 的 HMAC-SHA256 指纹。 */ - private String currentTokenFingerprint; - - /** 上一个 Refresh Token secret 的指纹,仅用于识别最近一次重放。 */ - private String previousTokenFingerprint; - -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/SessionView.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/SessionView.java deleted file mode 100644 index 482c4fc8f..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/SessionView.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.model; - -import lombok.Data; - -/** - * 可供系统管理模块使用的认证会话安全视图。 - * - *

不包含 Refresh Token、指纹、安全版本和轮换快照。

- * - * @author luoqiz - */ -@Data -public class SessionView { - - private String sessionId; - private Long userId; - private String username; - private String nickname; - private String clientId; - private String clientType; - private Long tenantId; - private long createdAt; - private long lastRefreshAt; - private String ip; - private String address; - private String browser; - private String os; - private long expiresAt; -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshAccessTokenIssuer.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshAccessTokenIssuer.java deleted file mode 100644 index d8aecc076..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshAccessTokenIssuer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.resp.LoginResp; - -/** - * 刷新时重新装载主体状态并签发 Access Token 的业务适配点。 - * - *

认证会话模块不依赖用户、客户端和租户实体;system 模块实现该接口,确保刷新时 - * 使用最新权限和业务状态。

- * - * @author luoqiz - */ -public interface RefreshAccessTokenIssuer { - - LoginResp issue(RefreshSession session, HttpServletRequest request, - HttpServletResponse response); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshTokenService.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshTokenService.java deleted file mode 100644 index 206884ae2..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/RefreshTokenService.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import top.continew.admin.auth.model.AuthSecurityVersion; -import top.continew.admin.auth.model.RefreshClientPolicy; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.resp.LoginResp; -import top.continew.admin.common.context.UserContext; -import top.continew.admin.common.context.UserExtraContext; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; - -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.List; - -/** - * Refresh Token 会话服务。 - * - * @author luoqiz - */ -public interface RefreshTokenService { - - /** 获取客户端 Refresh Token 的绝对有效期(秒)。 */ - long getRefreshTimeout(RefreshClientPolicy clientPolicy); - - /** 获取客户端 Refresh Token 的传输模式。 */ - RefreshTokenModeEnum getMode(RefreshClientPolicy clientPolicy); - - /** 为一次新登录生成 Session ID,供 Access Token 和 Refresh Token 共同绑定。 */ - String newSessionId(); - - /** - * 在固定作用域锁内复查登录状态、执行 Session 数量策略并创建会话。 - * - *

并发登录和最大登录数以 Refresh Session 为唯一事实源,不能依赖生命周期更短的 - * Access Token。当前浏览器中将被新 Cookie 覆盖的旧会话也在同一临界区内撤销。 - * 锁顺序固定为用户、租户、客户端,与安全配置失效共用同一组锁;状态复查函数也在 - * 锁内执行,调用方无法绕过临界区单独调用登录策略。

- * - * @param userId 初步认证得到的用户 ID,仅用于确定锁范围 - * @param clientId 初步认证得到的客户端 ID,仅用于确定锁范围 - * @param tenantId 租户 ID - * @param attemptFactory 锁内最终状态复查函数;接收需要固化到新 Session 的安全版本 - * @return 签发结果 - */ - T executeLoginPolicy(Long userId, String clientId, Long tenantId, - Function> attemptFactory); - - /** - * 创建登录会话的 Refresh Token。 - * - * @return BODY 模式需要返回给客户端的明文 Token;COOKIE 模式返回值仅供内部使用 - */ - String issue(String sessionId, UserContext userContext, RefreshClientPolicy clientPolicy, - UserExtraContext extraContext, AuthSecurityVersion securityVersion, - HttpServletResponse response, String accessToken, long accessTokenTimeout); - - /** - * 原子轮换 Refresh Token。 - * - * @param rawRefreshToken 客户端提交的明文 Refresh Token - * @param response 当前响应 - * @param accessTokenIssuer 根据旧会话重新签发 Access Token 的函数 - * @return 登录响应 - */ - LoginResp rotate(String rawRefreshToken, HttpServletResponse response, - Function accessTokenIssuer); - - /** 从 Cookie 或 BODY 中读取 Refresh Token;同时出现两种来源时拒绝请求。 */ - String resolve(String bodyRefreshToken, HttpServletRequest request); - - /** 在解析不可信 Token 之前按可信客户端地址执行刷新限流。 */ - void checkRequestRateLimit(HttpServletRequest request); - - /** 校验 Cookie 模式请求来源,防止跨站请求伪造刷新或退出当前登录。 */ - void validateRequest(String rawRefreshToken, HttpServletRequest request); - - /** 请求携带 Refresh Token Cookie 时,在解析 Cookie 前校验请求来源。 */ - void validateCookieOrigin(HttpServletRequest request); - - /** 撤销当前 Access Token 对应的 Refresh Session */ - void revokeCurrent(String accessToken, String refreshToken); - - /** 撤销用户的全部 Refresh Session */ - void revokeByUser(Long userId); - - /** 撤销租户的全部 Refresh Session */ - void revokeByTenant(Long tenantId); - - /** 撤销客户端的全部 Refresh Session */ - void revokeByClient(String clientId); - - /** 查询有效登录会话;tenantId 为空时查询全部租户。 */ - List listSessions(Long tenantId); - - /** 查询指定有效登录会话。 */ - RefreshSession getSession(String sessionId); - - /** 撤销指定 Refresh Session。 */ - void revokeBySessionId(String sessionId); - - /** 清理浏览器 Refresh Token Cookie */ - void clearCookie(HttpServletResponse response); - - /** - * 已在策略锁内完成最终状态复查的一次登录尝试。 - * - * @param userId 最终确认的用户 ID - * @param client 最终确认的客户端配置 - * @param currentAccessToken 当前请求携带的 Access Token - * @param currentRefreshToken 当前请求携带的 Refresh Token - * @param issuer 新令牌签发函数 - */ - record LoginAttempt(Long userId, RefreshClientPolicy clientPolicy, String currentAccessToken, - String currentRefreshToken, Supplier issuer) { - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionInvalidationService.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionInvalidationService.java deleted file mode 100644 index 6f2d47126..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionInvalidationService.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service; - -/** 认证会话失效入口。 */ -public interface SessionInvalidationService { - - void invalidateClient(String clientId); - - void invalidateTenant(Long tenantId); - - void invalidateUser(Long userId); - - void revokeSession(String sessionId); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionQueryService.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionQueryService.java deleted file mode 100644 index 9843739b0..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/SessionQueryService.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service; - -import top.continew.admin.auth.model.SessionView; - -import java.util.List; - -/** 认证会话安全查询入口。 */ -public interface SessionQueryService { - - SessionView getSession(String sessionId); - - List listSessions(Long tenantId); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/RefreshTokenServiceImpl.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/RefreshTokenServiceImpl.java deleted file mode 100644 index bc1314c2e..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/RefreshTokenServiceImpl.java +++ /dev/null @@ -1,808 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service.impl; - -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.StrUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.admin.auth.enums.LogoutReasonEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; -import top.continew.admin.auth.model.AuthSecurityVersion; -import top.continew.admin.auth.model.RefreshClientPolicy; -import top.continew.admin.auth.model.RefreshRotationResult; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.resp.LoginResp; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.service.RefreshTokenService.LoginAttempt; -import top.continew.admin.auth.support.AccessSessionCache; -import top.continew.admin.auth.support.RefreshSessionStore; -import top.continew.admin.auth.support.RefreshTokenCodec; -import top.continew.admin.auth.support.RefreshTokenRequestGuard; -import top.continew.admin.auth.support.AuthPolicyLock; -import top.continew.admin.auth.support.RefreshTokenCodec.IssuedToken; -import top.continew.admin.auth.support.RefreshTokenCodec.ParsedToken; -import top.continew.admin.auth.api.AuthSessionConstants; -import top.continew.admin.auth.api.AccessSessionValidator; -import top.continew.admin.auth.api.AuthSessionRevocationNotifier; -import top.continew.admin.common.context.UserContext; -import top.continew.admin.common.context.UserExtraContext; -import top.continew.admin.auth.exception.RefreshTokenException; -import top.continew.admin.auth.enums.SessionReplacementScope; -import top.continew.starter.cache.redisson.util.RedisLockUtils; -import top.continew.starter.core.exception.BusinessException; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * 基于单 Session 状态的 Refresh Token 实现。 - * - *

Refresh Token 使用 {@code sessionId.secret} 格式。轮换时只更新 Session 记录中的 - * current/previous 指纹,并用短时加密快照保证并发请求得到同一组 Token。未知 secret - * 只会认证失败,不会撤销 Session,避免攻击者利用公开 sessionId 强制用户下线。

- * - * @author luoqiz - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class RefreshTokenServiceImpl implements RefreshTokenService, AccessSessionValidator { - - private final RefreshTokenProperties properties; - private final RefreshTokenCodec tokenCodec; - private final RefreshSessionStore sessionStore; - private final RefreshTokenRequestGuard requestGuard; - private final AuthSessionRevocationNotifier sessionRevocationNotifier; - private final AccessSessionCache accessSessionCache; - - @Override - public long getRefreshTimeout(RefreshClientPolicy clientPolicy) { - return clientPolicy.refreshTokenTimeout(); - } - - @Override - public RefreshTokenModeEnum getMode(RefreshClientPolicy clientPolicy) { - return clientPolicy.refreshTokenMode(); - } - - @Override - public String newSessionId() { - return tokenCodec.newSessionId(); - } - - @Override - public T executeLoginPolicy(Long userId, String clientId, Long tenantId, - Function> attemptFactory) { - List> lockSuppliers = new ArrayList<>(3); - lockSuppliers.add(() -> sessionStore.lockUserPolicy(userId)); - if (tenantId != null) { - lockSuppliers.add(() -> sessionStore.lockTenantPolicyRead(tenantId)); - } - lockSuppliers.add(() -> sessionStore.lockClientPolicyRead(clientId)); - return this.executeWithLocks(lockSuppliers, () -> { - AuthSecurityVersion securityVersion = sessionStore.getSecurityVersion(userId, - clientId, tenantId); - LoginAttempt attempt = attemptFactory.apply(securityVersion); - this.requireValidLoginAttempt(userId, clientId, attempt); - this.applyLoginPolicy(attempt); - return attempt.issuer().get(); - }); - } - - private void applyLoginPolicy(LoginAttempt attempt) { - Long userId = attempt.userId(); - RefreshClientPolicy client = attempt.clientPolicy(); - // 浏览器新登录会覆盖现有 Cookie。无论客户端是否允许并发,都必须先撤销被 - // 覆盖的会话,避免服务端遗留一个客户端再也无法主动退出的长期凭证。 - Set replacedSessionIds = new LinkedHashSet<>(); - String accessSessionId = this.findAccessSessionId(attempt.currentAccessToken()); - if (accessSessionId != null) { - replacedSessionIds.add(accessSessionId); - } - String refreshSessionId = this.findAuthenticatedSessionId(attempt.currentRefreshToken()); - if (refreshSessionId != null) { - replacedSessionIds.add(refreshSessionId); - } - this.revokeSessions(replacedSessionIds, LogoutReasonEnum.REPLACED); - - List activeSessions = this.listActiveSessions(userId); - if (!client.concurrent()) { - SessionReplacementScope replacedRange = client.replacementScope(); - if (replacedRange == null) { - throw new BusinessException("客户端顶人下线范围配置无效"); - } - Set sessionIds = new LinkedHashSet<>(); - for (RefreshSession session : activeSessions) { - if (SessionReplacementScope.ALL_CLIENT_TYPES.equals(replacedRange) - || Objects.equals(client.clientType(), session.getClientType())) { - sessionIds.add(session.getSessionId()); - } - } - this.revokeSessions(sessionIds, LogoutReasonEnum.REPLACED); - } else { - // Web、App 和小程序分别控制并发数量,避免某一端的登录挤掉其他端。 - List sameClientTypeSessions = activeSessions.stream() - .filter(session -> Objects.equals(client.clientType(), session.getClientType())) - .toList(); - this.evictOverflowSessions(sameClientTypeSessions, client.maxLoginCount(), - client.overflowLogoutMode()); - } - } - - private void requireValidLoginAttempt(Long userId, String clientId, - LoginAttempt attempt) { - if (attempt == null || attempt.clientPolicy() == null || attempt.issuer() == null - || !Objects.equals(userId, attempt.userId()) - || !Objects.equals(clientId, attempt.clientPolicy().clientId())) { - throw new IllegalStateException("登录状态复查结果与认证锁范围不一致"); - } - } - - @Override - public String issue(String sessionId, UserContext userContext, RefreshClientPolicy client, - UserExtraContext extraContext, AuthSecurityVersion securityVersion, - HttpServletResponse response, String accessToken, long accessTokenTimeout) { - long now = System.currentTimeMillis(); - long refreshTimeout = this.getRefreshTimeout(client); - long expiresAt = this.calculateExpiresAt(now, refreshTimeout); - IssuedToken token = tokenCodec.issue(sessionId); - RefreshSession session = this.newSession(token, userContext, client, extraContext, - securityVersion, expiresAt); - try (RedisLockUtils ignored = sessionStore.lockSession(sessionId)) { - if (sessionStore.get(sessionId) != null) { - throw new BusinessException("登录会话创建失败,请重新登录"); - } - sessionStore.save(session); - sessionStore.index(session); - if (!sessionStore.isSecurityVersionCurrent(session)) { - this.revokeSessionLocked(session, LogoutReasonEnum.KICKOUT); - throw this.invalidToken(); - } - } catch (Exception e) { - this.revokeAfterFailure(sessionId); - throw e; - } - requestGuard.disableCaching(response); - if (RefreshTokenModeEnum.COOKIE.equals(session.getMode())) { - requestGuard.writeCookie(response, token.rawToken(), refreshTimeout); - } - return token.rawToken(); - } - - @Override - public LoginResp rotate(String rawRefreshToken, HttpServletResponse response, - Function accessTokenIssuer) { - ParsedToken presentedToken = tokenCodec.parse(rawRefreshToken); - // 在进入 Session 串行化区之前先验证凭证是否可能有效。否则只知道公开 sid 的 - // 攻击者可用随机 secret 长时间争抢该 Session 的锁,影响合法用户刷新。 - RefreshSession candidateSession = sessionStore.get(presentedToken.sessionId()); - if (!this.isKnownToken(candidateSession, presentedToken.fingerprint()) - && sessionStore.getRotation(presentedToken.fingerprint()) == null) { - throw this.invalidToken(); - } - // 会话已撤销或过期时,旧代指纹的轮换快照可能仍在宽限期内残留(R0→R1→R2 后 - // 撤销会话只清理 current/previous 两代快照)。此时凭证已无对应会话,必须按 - // 无效令牌处理,否则下方 candidateSession.getUserId() 会触发空指针。 - if (candidateSession == null) { - throw this.invalidToken(); - } - List> lockSuppliers = new ArrayList<>(3); - lockSuppliers.add(() -> sessionStore.lockUserPolicy(candidateSession.getUserId())); - if (candidateSession.getTenantId() != null) { - lockSuppliers - .add(() -> sessionStore.lockTenantPolicyRead(candidateSession.getTenantId())); - } - lockSuppliers.add(() -> sessionStore.lockClientPolicyRead(candidateSession.getClientId())); - return this.executeWithLocks(lockSuppliers, - () -> this.rotateLocked(presentedToken, response, accessTokenIssuer)); - } - - private LoginResp rotateLocked(ParsedToken presentedToken, HttpServletResponse response, - Function accessTokenIssuer) { - try (RedisLockUtils ignored = sessionStore.lockSession(presentedToken.sessionId())) { - RefreshSession session = this.requireActiveSession(presentedToken.sessionId()); - RefreshRotationResult cached = sessionStore.getRotation( - presentedToken.fingerprint()); - boolean current = tokenCodec.matches(presentedToken.fingerprint(), - session.getCurrentTokenFingerprint()); - boolean previous = tokenCodec.matches(presentedToken.fingerprint(), - session.getPreviousTokenFingerprint()); - // 只有能够证明持有当前/上一代 Token,或命中服务端短时加密快照的请求,才消耗 - // Session 级配额,避免攻击者仅凭公开 sid 耗尽合法会话的刷新次数。 - if (!current && !previous && cached == null) { - throw this.invalidToken(); - } - // 宽限期内整个 Session 只允许前进一代。后续请求统一返回最新结果,避免 - // R0→R1→R2 后迟到的 R0 响应把客户端 Cookie 回退到已经失效的 R1。 - RefreshRotationResult latest = sessionStore.getLatestRotation(session.getSessionId()); - if (latest != null && latest.isComplete() - && this.isCurrentRotationResult(session, latest)) { - return this.replayRotation(latest, session.getMode(), response); - } - if (cached != null && cached.isComplete()) { - if (this.isCurrentRotationResult(session, cached)) { - sessionStore.saveLatestRotation(session.getSessionId(), cached, - properties.getRotationGracePeriod()); - return this.replayRotation(cached, session.getMode(), response); - } - // 旧快照的输出已经不是当前代,不能再把陈旧凭证返回给客户端。 - throw this.invalidToken(); - } - if (previous && cached == null) { - // 上一代合法 Token 的宽限期已经结束,属于明确重放。撤销不能被会话 - // 限流挡住,否则攻击者可先耗尽配额,再让被盗旧 Token 延迟触发失效。 - this.revokeSessionLocked(session, LogoutReasonEnum.KICKOUT); - throw this.invalidToken(); - } - // 完整幂等快照已经优先返回,只有真正产生新 Access Token 的轮换才消耗配额。 - requestGuard.checkSessionRateLimit(session.getSessionId()); - - String newRefreshToken; - if (current) { - IssuedToken newToken = tokenCodec.issue(session.getSessionId()); - newRefreshToken = newToken.rawToken(); - // 先保存短时密文,再原子切换 Session。进程在任意一步退出,旧 Token 都能 - // 从快照恢复出同一个新 Token,不会产生客户端永远无法获得的会话状态。 - sessionStore.saveRotation(presentedToken.fingerprint(), - this.pendingRotation(newRefreshToken), - properties.getRotationGracePeriod()); - session.setPreviousTokenFingerprint(session.getCurrentTokenFingerprint()); - session.setCurrentTokenFingerprint(newToken.fingerprint()); - session.setLastRefreshAt(System.currentTimeMillis()); - sessionStore.save(session); - } else if (previous && cached != null) { - // 上次进程可能在 Session 已切换、Access Token 尚未签发时退出;从加密快照 - // 恢复完全相同的新 Refresh Token,并继续完成这一轮签发。 - newRefreshToken = tokenCodec.decrypt(cached.getEncryptedRefreshToken()); - this.requireCurrentToken(session, newRefreshToken); - } else { - // current/previous/cached 已在锁内完整分类,此分支仅保护未来改动不变量。 - throw this.invalidToken(); - } - - LoginResp loginResp = null; - try { - loginResp = accessTokenIssuer.apply(session); - this.requireCurrentSecurityVersion(session); - return this.completeRotationLocked(presentedToken, session, newRefreshToken, - loginResp, response); - } catch (RefreshTokenException e) { - if (loginResp != null && loginResp.getAccessToken() != null) { - this.kickoutQuietly(loginResp.getAccessToken()); - } - if (HttpStatus.UNAUTHORIZED.equals(e.getStatus())) { - this.revokeSessionLocked(session, LogoutReasonEnum.KICKOUT); - } - throw e; - } catch (Exception e) { - if (loginResp != null && loginResp.getAccessToken() != null) { - this.kickoutQuietly(loginResp.getAccessToken()); - } - // 基础设施临时故障不撤销仍然有效的 Session。保留待完成的新 Refresh - // Token,客户端用旧 Token 重试时可继续同一轮换,而不是被迫重新登录。 - this.preservePendingRotation(presentedToken, session, newRefreshToken); - throw e; - } - } - } - - private LoginResp completeRotationLocked(ParsedToken presentedToken, RefreshSession session, - String newRefreshToken, LoginResp loginResp, HttpServletResponse response) { - long now = System.currentTimeMillis(); - this.requireCurrentToken(session, newRefreshToken); - loginResp.setRefreshExpiresIn(this.remainingSeconds(session.getExpiresAt(), now)); - RefreshRotationResult completed = this.completedRotation(loginResp, newRefreshToken); - sessionStore.saveLatestRotation(session.getSessionId(), completed, - properties.getRotationGracePeriod()); - sessionStore.saveRotation(presentedToken.fingerprint(), completed, - properties.getRotationGracePeriod()); - if (RefreshTokenModeEnum.BODY.equals(session.getMode())) { - loginResp.setRefreshToken(newRefreshToken); - } else { - requestGuard.writeCookie(response, newRefreshToken, loginResp.getRefreshExpiresIn()); - } - requestGuard.disableCaching(response); - return loginResp; - } - - @Override - public String resolve(String bodyRefreshToken, HttpServletRequest request) { - return requestGuard.resolve(bodyRefreshToken, request); - } - - @Override - public void checkRequestRateLimit(HttpServletRequest request) { - requestGuard.checkRequestRateLimit(request); - } - - @Override - public void validateRequest(String rawRefreshToken, HttpServletRequest request) { - requestGuard.validateRequest(rawRefreshToken, request); - } - - @Override - public void validateCookieOrigin(HttpServletRequest request) { - requestGuard.validateCookieOrigin(request); - } - - @Override - public void revokeCurrent(String accessToken, String refreshToken) { - Set sessionIds = new LinkedHashSet<>(); - String accessSessionId = this.findAccessSessionId(accessToken); - if (accessSessionId != null) { - sessionIds.add(accessSessionId); - } - String refreshSessionId = this.findAuthenticatedSessionId(refreshToken); - if (refreshSessionId != null) { - sessionIds.add(refreshSessionId); - } - sessionIds.forEach(sessionId -> this.revokeSession(sessionId, - LogoutReasonEnum.LOGOUT)); - } - - @Override - public void revokeByUser(Long userId) { - if (userId != null) { - this.invalidateSessions(() -> sessionStore.lockUserPolicy(userId), - () -> sessionStore.incrementUserSecurityVersion(userId), - () -> sessionStore.findByUser(userId)); - } - } - - @Override - public void revokeByTenant(Long tenantId) { - if (tenantId != null) { - this.invalidateSessions(() -> sessionStore.lockTenantPolicyWrite(tenantId), - () -> sessionStore.incrementTenantSecurityVersion(tenantId), - () -> sessionStore.findByTenant(tenantId)); - } - } - - @Override - public void revokeByClient(String clientId) { - if (clientId != null) { - this.invalidateSessions(() -> sessionStore.lockClientPolicyWrite(clientId), - () -> sessionStore.incrementClientSecurityVersion(clientId), - () -> sessionStore.findByClient(clientId)); - } - } - - @Override - public List listSessions(Long tenantId) { - Set sessionIds = tenantId == null ? sessionStore.findAll() - : sessionStore.findByTenant(tenantId); - List sessions = new ArrayList<>(sessionIds.size()); - for (String sessionId : sessionIds) { - RefreshSession session = this.getSession(sessionId); - if (session != null - && (tenantId == null || Objects.equals(tenantId, session.getTenantId()))) { - sessions.add(session); - } - } - sessions.sort(Comparator.comparingLong(RefreshSession::getCreatedAt).reversed() - .thenComparing(RefreshSession::getSessionId)); - return sessions; - } - - @Override - public RefreshSession getSession(String sessionId) { - if (StrUtil.isBlank(sessionId)) { - return null; - } - RefreshSession session = sessionStore.get(sessionId); - if (session == null) { - return null; - } - if (session.getExpiresAt() <= System.currentTimeMillis() - || !sessionStore.isSecurityVersionCurrent(session)) { - this.revokeSession(sessionId, null); - return null; - } - return session; - } - - @Override - public void revokeBySessionId(String sessionId) { - if (StrUtil.isNotBlank(sessionId)) { - this.revokeSession(sessionId, LogoutReasonEnum.KICKOUT); - } - } - - @Override - public String getInvalidReason(String accessToken) { - if (StrUtil.isBlank(accessToken)) { - return null; - } - String claimedSessionId = Convert.toStr(StpUtil.getExtra(accessToken, - AuthSessionConstants.SESSION_ID_CLAIM)); - if (StrUtil.isBlank(claimedSessionId)) { - return LogoutReasonEnum.LOGOUT.getMessage(); - } - // 快路径:本地缓存命中「会话有效」,跳过 2 次 Redis 往返;撤销经广播失效并由 - // 本地缓存 TTL 兜底,未命中一律回源全量校验,正确性不依赖缓存。 - if (accessSessionCache.isValid(claimedSessionId)) { - return null; - } - RefreshSession session = sessionStore.get(claimedSessionId); - Long loginId = Convert.toLong(StpUtil.getLoginIdByToken(accessToken)); - boolean invalid = session == null - || session.getExpiresAt() <= System.currentTimeMillis() - || !sessionStore.isSecurityVersionCurrent(session) - || !Objects.equals(session.getUserId(), loginId); - if (!invalid) { - accessSessionCache.markValid(claimedSessionId); - return null; - } - // 只有会话已经失效时才读取撤销原因,健康请求不会因此多一次 Redis 往返。 - LogoutReasonEnum reason = sessionStore.getLogoutReason(claimedSessionId); - return reason == null ? LogoutReasonEnum.LOGOUT.getMessage() : reason.getMessage(); - } - - @Override - public void clearCookie(HttpServletResponse response) { - requestGuard.clearCookie(response); - } - - private RefreshSession newSession(IssuedToken token, UserContext userContext, - RefreshClientPolicy client, UserExtraContext extraContext, - AuthSecurityVersion securityVersion, - long expiresAt) { - long now = System.currentTimeMillis(); - RefreshSession session = new RefreshSession(); - session.setSessionId(token.sessionId()); - session.setUserId(userContext.getId()); - session.setUsername(userContext.getUsername()); - session.setNickname(userContext.getNickname()); - session.setClientId(client.clientId()); - session.setClientType(client.clientType()); - session.setTenantId(userContext.getTenantId()); - session.setMode(this.getMode(client)); - session.setCreatedAt(now); - session.setLastRefreshAt(now); - session.setIp(extraContext.getIp()); - session.setAddress(extraContext.getAddress()); - session.setBrowser(extraContext.getBrowser()); - session.setOs(extraContext.getOs()); - session.setExpiresAt(expiresAt); - session.setUserSecurityVersion(securityVersion.userVersion()); - session.setClientSecurityVersion(securityVersion.clientVersion()); - session.setTenantSecurityVersion(securityVersion.tenantVersion()); - session.setCurrentTokenFingerprint(token.fingerprint()); - return session; - } - - private RefreshSession requireActiveSession(String sessionId) { - RefreshSession session = sessionStore.get(sessionId); - if (session == null || session.getExpiresAt() <= System.currentTimeMillis()) { - throw this.invalidToken(); - } - if (!sessionStore.isSecurityVersionCurrent(session)) { - this.revokeSessionLocked(session, LogoutReasonEnum.KICKOUT); - throw this.invalidToken(); - } - return session; - } - - private void requireCurrentSecurityVersion(RefreshSession session) { - if (session.getExpiresAt() <= System.currentTimeMillis() - || !sessionStore.isSecurityVersionCurrent(session)) { - throw this.invalidToken(); - } - } - - private void requireCurrentToken(RefreshSession session, String rawToken) { - ParsedToken token = tokenCodec.parse(rawToken); - if (!Objects.equals(session.getSessionId(), token.sessionId()) - || !tokenCodec.matches(token.fingerprint(), session.getCurrentTokenFingerprint())) { - throw this.invalidToken(); - } - } - - private boolean isKnownToken(RefreshSession session, String fingerprint) { - return session != null && (tokenCodec.matches(fingerprint, - session.getCurrentTokenFingerprint()) - || tokenCodec.matches(fingerprint, - session.getPreviousTokenFingerprint())); - } - - private String findAuthenticatedSessionId(String refreshToken) { - if (StrUtil.isBlank(refreshToken)) { - return null; - } - ParsedToken token; - try { - token = tokenCodec.parse(refreshToken); - } catch (BusinessException e) { - return null; - } - RefreshSession session = sessionStore.get(token.sessionId()); - return (this.isKnownToken(session, token.fingerprint()) - || session != null && sessionStore.getRotation(token.fingerprint()) != null) - ? token.sessionId() - : null; - } - - private String findAccessSessionId(String accessToken) { - if (StrUtil.isBlank(accessToken)) { - return null; - } - return Convert.toStr(StpUtil.getExtra(accessToken, AuthSessionConstants.SESSION_ID_CLAIM)); - } - - private void invalidateSessions(Supplier policyLock, - Runnable incrementVersion, - Supplier> sessionIds) { - this.executeWithLocks(List.of(policyLock), () -> { - incrementVersion.run(); - this.revokeSessions(sessionIds.get(), LogoutReasonEnum.KICKOUT); - return null; - }); - } - - /** - * 获取一组固定顺序的分布式锁。若处于数据库事务中,锁延迟到事务完成后释放,保证 - * 其他登录只能看到事务提交前的旧状态或提交后的新状态,不能落入中间窗口。 - */ - private T executeWithLocks(List> lockSuppliers, - Supplier action) { - List locks = new ArrayList<>(lockSuppliers.size()); - boolean transactionManaged = false; - try { - for (Supplier lockSupplier : lockSuppliers) { - locks.add(lockSupplier.get()); - } - if (TransactionSynchronizationManager.isActualTransactionActive()) { - if (!TransactionSynchronizationManager.isSynchronizationActive()) { - throw new IllegalStateException("当前事务不支持认证策略锁同步"); - } - TransactionSynchronizationManager.registerSynchronization( - new TransactionSynchronization() { - - @Override - public void afterCompletion(int status) { - releaseLocks(locks); - } - }); - transactionManaged = true; - } - return action.get(); - } finally { - if (!transactionManaged) { - this.releaseLocks(locks); - } - } - } - - private void releaseLocks(List locks) { - for (int i = locks.size() - 1; i >= 0; i--) { - try { - locks.get(i).close(); - } catch (RuntimeException e) { - log.warn("释放认证策略锁失败", e); - } - } - } - - /** - * 批量撤销会话并记录失效原因。 - * - * @param sessionIds 待撤销的 Refresh Session ID - * @param reason 客户端可见的失效原因,null 表示不记录 - */ - private void revokeSessions(Set sessionIds, LogoutReasonEnum reason) { - RuntimeException firstFailure = null; - for (String sessionId : sessionIds) { - try { - this.revokeSession(sessionId, reason); - } catch (RuntimeException e) { - log.warn("撤销 Refresh Session [{}] 失败", sessionId, e); - if (firstFailure == null) { - firstFailure = e; - } - } - } - if (firstFailure != null) { - throw firstFailure; - } - } - - /** 加载安全版本仍有效的用户会话,并清理失效记录。 */ - private List listActiveSessions(Long userId) { - List sessions = new ArrayList<>(); - for (String sessionId : sessionStore.findByUser(userId)) { - RefreshSession session = sessionStore.get(sessionId); - if (session == null || !Objects.equals(userId, session.getUserId())) { - continue; - } - if (session.getExpiresAt() <= System.currentTimeMillis() - || !sessionStore.isSecurityVersionCurrent(session)) { - this.revokeSession(sessionId, null); - continue; - } - sessions.add(session); - } - sessions.sort(Comparator.comparingLong(RefreshSession::getCreatedAt) - .thenComparing(RefreshSession::getSessionId)); - return sessions; - } - - /** - * 为本次新登录预留一个名额,稳定淘汰创建时间最早的 Refresh Session。 - * - *

被淘汰会话看到的提示由客户端配置的注销模式决定:Sa-Token 不再参与并发控制, - * 这里必须自行把 {@code overflowLogoutMode} 落成失效原因。

- */ - private void evictOverflowSessions(List activeSessions, - Integer maxLoginCount, LogoutReasonEnum overflowLogoutMode) { - if (maxLoginCount == null || maxLoginCount == -1) { - return; - } - if (maxLoginCount < 1) { - throw new BusinessException("客户端最大登录数量必须为 -1 或正整数"); - } - int overflowCount = activeSessions.size() - maxLoginCount + 1; - if (overflowCount <= 0) { - return; - } - Set sessionIds = new LinkedHashSet<>(); - activeSessions.stream() - .limit(overflowCount) - .map(RefreshSession::getSessionId) - .forEach(sessionIds::add); - this.revokeSessions(sessionIds, - overflowLogoutMode == null ? LogoutReasonEnum.REPLACED : overflowLogoutMode); - } - - private void revokeSession(String sessionId, LogoutReasonEnum reason) { - try (RedisLockUtils ignored = sessionStore.lockSession(sessionId)) { - RefreshSession session = sessionStore.get(sessionId); - if (session != null) { - this.revokeSessionLocked(session, reason); - } - } - } - - private void revokeSessionLocked(RefreshSession session, LogoutReasonEnum reason) { - String currentFingerprint = session.getCurrentTokenFingerprint(); - String previousFingerprint = session.getPreviousTokenFingerprint(); - // 先写失效原因再删会话,保证被撤销的客户端下一次请求能读到成立的原因。 - sessionStore.saveLogoutReason(session.getSessionId(), reason); - sessionStore.delete(session.getSessionId()); - sessionStore.deleteRotation(currentFingerprint); - sessionStore.deleteRotation(previousFingerprint); - sessionStore.deleteLatestRotation(session.getSessionId()); - try { - sessionStore.removeIndexes(session); - } catch (RuntimeException e) { - // Session 已删除;索引清理失败只会产生会自动过期的脏数据,不会让 Token 重新有效。 - log.warn("清理 Refresh Session [{}] 管理索引失败", session.getSessionId(), e); - } - sessionRevocationNotifier.notifyRevoked(session.getSessionId()); - // 清除热路径本地缓存并广播通知其他节点,撤销即时性由广播与本地 TTL 共同保证。 - accessSessionCache.invalidate(session.getSessionId()); - } - - private void revokeAfterFailure(String sessionId) { - try { - // 登录签发失败时客户端还没拿到任何凭证,不需要记录失效原因。 - this.revokeSession(sessionId, null); - } catch (RuntimeException revokeException) { - log.error("失败补偿时撤销 Refresh Session [{}] 失败", sessionId, revokeException); - } - } - - private void kickoutQuietly(String accessToken) { - try { - StpUtil.kickoutByTokenValue(accessToken); - } catch (RuntimeException e) { - log.warn("刷新失败后清理 Access Token 失败", e); - } - } - - private RefreshRotationResult pendingRotation(String newRefreshToken) { - RefreshRotationResult result = new RefreshRotationResult(); - result.setEncryptedRefreshToken(tokenCodec.encrypt(newRefreshToken)); - return result; - } - - private RefreshRotationResult completedRotation(LoginResp loginResp, String refreshToken) { - RefreshRotationResult result = this.pendingRotation(refreshToken); - result.setEncryptedAccessToken(tokenCodec.encrypt(loginResp.getAccessToken())); - result.setExpiresIn(loginResp.getExpiresIn()); - result.setRefreshExpiresIn(loginResp.getRefreshExpiresIn()); - result.setTenantId(loginResp.getTenantId()); - return result; - } - - private boolean isCurrentRotationResult(RefreshSession session, - RefreshRotationResult result) { - String refreshToken = tokenCodec.decrypt(result.getEncryptedRefreshToken()); - ParsedToken parsedToken = tokenCodec.parse(refreshToken); - return Objects.equals(session.getSessionId(), parsedToken.sessionId()) - && tokenCodec.matches(parsedToken.fingerprint(), - session.getCurrentTokenFingerprint()); - } - - private void preservePendingRotation(ParsedToken presentedToken, RefreshSession session, - String newRefreshToken) { - RefreshRotationResult pending = this.pendingRotation(newRefreshToken); - try { - sessionStore.saveLatestRotation(session.getSessionId(), pending, - properties.getRotationGracePeriod()); - sessionStore.saveRotation(presentedToken.fingerprint(), pending, - properties.getRotationGracePeriod()); - } catch (RuntimeException persistenceException) { - log.warn("保存 Refresh Session [{}] 待恢复轮换状态失败", session.getSessionId(), - persistenceException); - } - } - - private LoginResp replayRotation(RefreshRotationResult result, RefreshTokenModeEnum mode, - HttpServletResponse response) { - String accessToken = tokenCodec.decrypt(result.getEncryptedAccessToken()); - String refreshToken = tokenCodec.decrypt(result.getEncryptedRefreshToken()); - LoginResp loginResp = LoginResp.builder() - .accessToken(accessToken) - .tokenType("Bearer") - .expiresIn(result.getExpiresIn()) - .refreshExpiresIn(result.getRefreshExpiresIn()) - .tenantId(result.getTenantId()) - .build(); - if (RefreshTokenModeEnum.BODY.equals(mode)) { - loginResp.setRefreshToken(refreshToken); - } else { - requestGuard.writeCookie(response, refreshToken, result.getRefreshExpiresIn()); - } - requestGuard.disableCaching(response); - return loginResp; - } - - private long calculateExpiresAt(long now, long timeoutSeconds) { - if (timeoutSeconds > (Long.MAX_VALUE - now) / 1000) { - throw new BusinessException("令牌有效期超出系统支持范围"); - } - return now + timeoutSeconds * 1000; - } - - private long remainingSeconds(long expiresAt, long now) { - long remainingMillis = expiresAt - now; - if (remainingMillis <= 0) { - return 1; - } - return remainingMillis / 1000 + (remainingMillis % 1000 == 0 ? 0 : 1); - } - - private RefreshTokenException invalidToken() { - return RefreshTokenException.unauthorized("登录状态已失效,请重新登录"); - } - -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionInvalidationServiceImpl.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionInvalidationServiceImpl.java deleted file mode 100644 index d569bdc01..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionInvalidationServiceImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service.impl; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.service.SessionInvalidationService; - -/** 认证会话失效服务实现。 */ -@Service -@RequiredArgsConstructor -public class SessionInvalidationServiceImpl implements SessionInvalidationService { - - private final RefreshTokenService refreshTokenService; - - @Override - public void invalidateClient(String clientId) { - this.afterCommit(() -> refreshTokenService.revokeByClient(clientId)); - } - - @Override - public void invalidateTenant(Long tenantId) { - this.afterCommit(() -> refreshTokenService.revokeByTenant(tenantId)); - } - - @Override - public void invalidateUser(Long userId) { - this.afterCommit(() -> refreshTokenService.revokeByUser(userId)); - } - - @Override - public void revokeSession(String sessionId) { - this.afterCommit(() -> refreshTokenService.revokeBySessionId(sessionId)); - } - - /** - * 业务数据提交后才撤销登录态。认证策略写锁由外层切面持有到事务 afterCompletion - * 之后才释放;这里的 afterCommit(先于 afterCompletion 执行)会重新获取同一个 - * lockTenantPolicyWrite / lockUserPolicy / lockClientPolicyWrite,依赖 Redisson - * 同线程写锁可重入。该不变量保证“数据库已提交而新登录会话穿过旧策略”的窗口不会 - * 出现;改动本方法时不得把撤销时机移出写锁持有区间。 - */ - private void afterCommit(Runnable action) { - if (!TransactionSynchronizationManager.isSynchronizationActive()) { - action.run(); - return; - } - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - - @Override - public void afterCommit() { - action.run(); - } - }); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionQueryServiceImpl.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionQueryServiceImpl.java deleted file mode 100644 index 67c35e8fb..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/service/impl/SessionQueryServiceImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service.impl; - -import cn.hutool.core.bean.BeanUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.SessionView; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.service.SessionQueryService; - -import java.util.List; - -/** 认证会话查询服务实现。 */ -@Service -@RequiredArgsConstructor -public class SessionQueryServiceImpl implements SessionQueryService { - - private final RefreshTokenService refreshTokenService; - - @Override - public SessionView getSession(String sessionId) { - RefreshSession session = refreshTokenService.getSession(sessionId); - return session == null ? null : BeanUtil.copyProperties(session, SessionView.class); - } - - @Override - public List listSessions(Long tenantId) { - return BeanUtil.copyToList(refreshTokenService.listSessions(tenantId), SessionView.class); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AccessSessionCache.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AccessSessionCache.java deleted file mode 100644 index 53f6f58c4..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AccessSessionCache.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import cn.hutool.core.util.StrUtil; -import com.alicp.jetcache.Cache; -import com.alicp.jetcache.embedded.CaffeineCacheBuilder; -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.redisson.api.RTopic; -import org.redisson.api.RedissonClient; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthSessionConstants; -import top.continew.admin.auth.config.RefreshTokenProperties; - -import java.util.concurrent.TimeUnit; - -/** - * 会话有效结论的本地缓存:热路径 0 Redis、撤销广播失效、TTL 兜底。 - * - *

只缓存「会话有效」结果;未命中一律回源全量校验,正确性不依赖本缓存。撤销通过 - * {@link #invalidate(String)} 同时清除本节点缓存并广播,其余节点收到后清除各自缓存; - * 广播失败时由本地 TTL(2 秒)兜底,撤销延迟最坏 2 秒。

- * - *

广播 Topic 默认按应用名隔离({@code auth:access-session-invalid:{spring.application.name}}): - * 同一服务的多副本共用 Topic(必须互相失效),不同服务天然隔离(互不串扰);多个服务 - * 有意共享同一会话域时可通过 {@code auth.refresh-token.access-session-invalid-topic} - * 显式覆盖为公共 Topic。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Slf4j -@Component -public class AccessSessionCache { - - /** 本地缓存 TTL:同时是撤销延迟的最坏兜底上限 */ - private static final long CACHE_TTL_SECONDS = 2; - private static final int LOCAL_LIMIT = 100_000; - - private final RefreshTokenProperties properties; - private final RedissonClient redissonClient; - private final String topicName; - private Cache validCache; - private RTopic invalidTopic; - - public AccessSessionCache(RefreshTokenProperties properties, RedissonClient redissonClient, - @Value("${spring.application.name:unknown}") String applicationName) { - this.properties = properties; - this.redissonClient = redissonClient; - // 未显式配置时按应用名隔离;应用名缺失时退化为 unknown,多服务场景应显式配置 - // topic 或保证 spring.application.name 唯一。 - this.topicName = StrUtil.blankToDefault(properties.getAccessSessionInvalidTopic(), - AuthSessionConstants.ACCESS_SESSION_INVALID_TOPIC_PREFIX + ":" + StrUtil - .blankToDefault(applicationName, "unknown")); - } - - @PostConstruct - void init() { - this.validCache = CaffeineCacheBuilder.createCaffeineCacheBuilder() - .expireAfterWrite(CACHE_TTL_SECONDS, TimeUnit.SECONDS) - .limit(LOCAL_LIMIT) - .buildCache(); - if (properties.isAccessSessionCacheEnabled()) { - this.invalidTopic = redissonClient.getTopic(this.topicName); - this.invalidTopic.addListener(String.class, (channel, sessionId) -> { - // 防御性校验:topic 被错误共享时忽略空消息/非法载荷,只处理形如会话 ID 的消息 - if (StrUtil.isBlank(sessionId)) { - return; - } - validCache.remove(sessionId); - log.debug("收到会话 [{}] 失效广播,已清除本地缓存", sessionId); - }); - } - } - - /** 热路径:会话是否缓存为「有效」。未命中或未启用返回 false,走回源校验。 */ - public boolean isValid(String sessionId) { - return properties.isAccessSessionCacheEnabled() && StrUtil.isNotBlank(sessionId) - && Boolean.TRUE.equals(validCache.get(sessionId)); - } - - /** 回源校验通过后写入本地缓存。 */ - public void markValid(String sessionId) { - if (properties.isAccessSessionCacheEnabled() && StrUtil.isNotBlank(sessionId)) { - validCache.put(sessionId, Boolean.TRUE); - } - } - - /** 会话失效:清除本节点缓存并广播通知其他节点。 */ - public void invalidate(String sessionId) { - if (!properties.isAccessSessionCacheEnabled() || StrUtil.isBlank(sessionId)) { - return; - } - validCache.remove(sessionId); - try { - invalidTopic.publish(sessionId); - } catch (RuntimeException e) { - // 广播失败时本地缓存已清除,其他节点由 2s TTL 兜底,不会无限期放行。 - log.warn("广播会话 [{}] 失效失败,将由本地缓存 TTL 兜底", sessionId, e); - } - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyLock.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyLock.java deleted file mode 100644 index d582fef1c..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyLock.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.redisson.api.RLock; -import top.continew.admin.auth.exception.RefreshTokenException; - -import java.util.concurrent.TimeUnit; - -/** - * 项目内认证策略锁。 - * - *

支持 Redisson 普通锁和读写锁,避免为认证并发策略修改 ContiNew Starter。

- * - * @author luoqiz - * @since 4.2.0 - */ -public final class AuthPolicyLock implements AutoCloseable { - - private final RLock lock; - private final boolean acquired; - - private AuthPolicyLock(RLock lock, boolean acquired) { - this.lock = lock; - this.acquired = acquired; - } - - /** 在指定时间内获取锁,并使用 Redisson watchdog 自动续期。 */ - public static AuthPolicyLock acquire(RLock lock, long waitMillis) { - boolean acquired; - try { - acquired = lock.tryLock(waitMillis, -1, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw RefreshTokenException.internalServerError("认证请求已中断"); - } - if (!acquired) { - throw RefreshTokenException.tooManyRequests("认证请求正在处理中,请稍后重试"); - } - return new AuthPolicyLock(lock, true); - } - - @Override - public void close() { - if (acquired && lock.isHeldByCurrentThread()) { - lock.unlock(); - } - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyWriteLockAspect.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyWriteLockAspect.java deleted file mode 100644 index 1a35461d8..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/AuthPolicyWriteLockAspect.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.aop.support.AopUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -/** - * 认证策略写锁协调器。 - * - *

切面只理解通用策略锁目标。用户、客户端、部门、租户和套餐到目标的映射由各 - * 业务模块的 {@link AuthPolicyLockTargetResolver} 提供,因此认证会话模块不会反向 - * 依赖业务 Mapper。锁在事务代理外层获得,顺序固定为“策略锁 → 数据库事务 → 会话 - * 失效”。

- * - * @author luoqiz - */ -@Aspect -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -@RequiredArgsConstructor -@Slf4j -public class AuthPolicyWriteLockAspect { - - private final ApplicationContext applicationContext; - private final RefreshSessionStore sessionStore; - - /** - * 在事务开始前获取策略写锁,并在事务完成后释放。 - * - * @param joinPoint 当前业务方法 - * @return 业务方法返回值 - * @throws Throwable 业务方法异常 - */ - @Around("@annotation(top.continew.admin.auth.api.AuthPolicyWriteLocked)") - public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { - if (TransactionSynchronizationManager.isActualTransactionActive()) { - throw new IllegalStateException("认证策略变更必须在数据库事务外发起"); - } - AuthPolicyWriteLocked locked = this.getPolicyWriteLocked(joinPoint); - AuthPolicyLockTargetResolver resolver = applicationContext.getBean(locked.value()); - List targets = resolver.resolve(joinPoint.getArgs()).stream() - .filter(Objects::nonNull) - .filter(target -> target.key() != null && !target.key().isBlank()) - .distinct() - .sorted(Comparator.comparing(AuthPolicyLockTarget::type) - .thenComparing(AuthPolicyLockTarget::key)) - .toList(); - List locks = new ArrayList<>(targets.size()); - try { - for (AuthPolicyLockTarget target : targets) { - locks.add(this.lock(target)); - } - return joinPoint.proceed(); - } finally { - for (int i = locks.size() - 1; i >= 0; i--) { - try { - locks.get(i).close(); - } catch (RuntimeException e) { - log.warn("释放认证策略写锁失败", e); - } - } - } - } - - private AuthPolicyLock lock(AuthPolicyLockTarget target) { - return switch (target.type()) { - case USER -> sessionStore.lockUserPolicy(Long.parseLong(target.key())); - case TENANT -> sessionStore.lockTenantPolicyWrite(Long.parseLong(target.key())); - case CLIENT -> sessionStore.lockClientPolicyWrite(target.key()); - }; - } - - private AuthPolicyWriteLocked getPolicyWriteLocked(ProceedingJoinPoint joinPoint) { - Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); - Method targetMethod = - AopUtils.getMostSpecificMethod(method, joinPoint.getTarget().getClass()); - AuthPolicyWriteLocked locked = AnnotationUtils.findAnnotation(targetMethod, - AuthPolicyWriteLocked.class); - if (locked == null) { - throw new IllegalStateException("认证策略写锁声明缺失"); - } - return locked; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshSessionStore.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshSessionStore.java deleted file mode 100644 index a8a07e725..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshSessionStore.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import cn.hutool.json.JSONUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.redisson.api.RScoredSortedSet; -import org.redisson.api.RScript; -import org.redisson.api.RBatch; -import org.redisson.api.RFuture; -import org.redisson.api.RedissonClient; -import org.redisson.client.codec.StringCodec; -import org.springframework.stereotype.Component; -import top.continew.admin.auth.enums.LogoutReasonEnum; -import top.continew.admin.auth.model.AuthSecurityVersion; -import top.continew.admin.auth.model.RefreshRotationResult; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.exception.RefreshTokenException; -import top.continew.starter.cache.redisson.util.RedisLockUtils; -import top.continew.starter.cache.redisson.util.RedisUtils; - -import java.time.Duration; -import java.time.Instant; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Refresh Session Redis 仓储。 - * - *

轮换状态全部集中在 {@code SESSION:{sid}} 单条记录中。业务层持有 Session 锁时, - * 一次 Redis SET 即完成当前/上一个 Token 的原子切换,不再维护 Token 链和 family 指针。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class RefreshSessionStore { - - /** 新部署会话存储命名空间;不读取任何历史 Refresh Token 状态。 */ - private static final String KEY_PREFIX = "AUTH:REFRESH:V1:"; - private static final String SESSION_PREFIX = KEY_PREFIX + "DATA:"; - private static final String ALL_INDEX_KEY = KEY_PREFIX + "ALL"; - private static final String USER_INDEX_PREFIX = KEY_PREFIX + "USER:"; - private static final String TENANT_INDEX_PREFIX = KEY_PREFIX + "TENANT:"; - private static final String CLIENT_INDEX_PREFIX = KEY_PREFIX + "CLIENT:"; - private static final String ROTATION_PREFIX = KEY_PREFIX + "ROTATION:"; - private static final String LOGOUT_REASON_PREFIX = KEY_PREFIX + "REASON:"; - private static final String LATEST_ROTATION_PREFIX = KEY_PREFIX + "ROTATION:LATEST:"; - private static final String SESSION_LOCK_PREFIX = KEY_PREFIX + "LOCK:SESSION:"; - private static final String USER_POLICY_LOCK_PREFIX = KEY_PREFIX + "LOCK:USER:"; - private static final String TENANT_POLICY_LOCK_PREFIX = KEY_PREFIX + "LOCK:TENANT:"; - private static final String CLIENT_POLICY_LOCK_PREFIX = KEY_PREFIX + "LOCK:CLIENT:"; - private static final String USER_VERSION_PREFIX = KEY_PREFIX + "VERSION:USER:"; - private static final String TENANT_VERSION_PREFIX = KEY_PREFIX + "VERSION:TENANT:"; - private static final String CLIENT_VERSION_PREFIX = KEY_PREFIX + "VERSION:CLIENT:"; - private static final long LOCK_WAIT_MILLIS = 5000L; - /** - * 失效原因标记保留时长(秒)。 - * - *

标记只用于在会话被撤销后向客户端返回更有针对性的提示,不影响任何鉴权判定; - * 过期后请求回落到默认提示,因此不需要跟随 Refresh Token 的生命周期。

- */ - private static final long LOGOUT_REASON_TTL_SECONDS = 300L; - private static final String RATE_LIMIT_SCRIPT = "local current = redis.call('INCR', KEYS[1]); " - + "if current == 1 then redis.call('PEXPIRE', KEYS[1], ARGV[1]); end; " - + "return current;"; - - private final RedissonClient redissonClient; - - /** 按 Session ID 读取会话。 */ - public RefreshSession get(String sessionId) { - Object value = RedisUtils.get(SESSION_PREFIX + sessionId); - if (value == null) { - return null; - } - try { - return JSONUtil.toBean(value.toString(), RefreshSession.class); - } catch (RuntimeException e) { - // 记录损坏(手工改库、滚动发布跨版本写入等)时按「会话不存在」处理, - // 配合 getSession/revokeSession 的懒清理删除脏记录,避免热路径 500。 - log.warn("反序列化 Refresh Session [{}] 失败,按失效会话处理", sessionId, e); - return null; - } - } - - /** 保存完整 Session;Redis SET 本身是单 Key 原子操作。 */ - public void save(RefreshSession session) { - String key = SESSION_PREFIX + session.getSessionId(); - String value = JSONUtil.toJsonStr(session); - RedisUtils.set(key, value, - Duration.ofSeconds(this.remainingSeconds(session.getExpiresAt()))); - } - - /** 删除 Session;Session 不存在即代表该登录会话失效。 */ - public void delete(String sessionId) { - RedisUtils.delete(SESSION_PREFIX + sessionId); - } - - /** 建立用户、租户和客户端到 Session 的管理索引。 */ - public void index(RefreshSession session) { - this.addToIndex(ALL_INDEX_KEY, session.getSessionId(), session.getExpiresAt()); - this.addToIndex(USER_INDEX_PREFIX + session.getUserId(), session.getSessionId(), - session.getExpiresAt()); - if (session.getTenantId() != null) { - this.addToIndex(TENANT_INDEX_PREFIX + session.getTenantId(), session.getSessionId(), - session.getExpiresAt()); - } - this.addToIndex(CLIENT_INDEX_PREFIX + session.getClientId(), session.getSessionId(), - session.getExpiresAt()); - } - - /** 查询指定用户的全部 Refresh Session ID。 */ - public Set findByUser(Long userId) { - return this.findByIndex(USER_INDEX_PREFIX + userId); - } - - /** 查询全部有效 Refresh Session ID。 */ - public Set findAll() { - return this.findByIndex(ALL_INDEX_KEY); - } - - /** 查询指定租户的全部 Refresh Session ID。 */ - public Set findByTenant(Long tenantId) { - return this.findByIndex(TENANT_INDEX_PREFIX + tenantId); - } - - /** 查询指定客户端的全部 Refresh Session ID。 */ - public Set findByClient(String clientId) { - return this.findByIndex(CLIENT_INDEX_PREFIX + clientId); - } - - /** 从所有管理索引移除指定 Session。 */ - public void removeIndexes(RefreshSession session) { - this.getIndex(ALL_INDEX_KEY).remove(session.getSessionId()); - this.getIndex(USER_INDEX_PREFIX + session.getUserId()).remove(session.getSessionId()); - if (session.getTenantId() != null) { - this.getIndex(TENANT_INDEX_PREFIX + session.getTenantId()) - .remove(session.getSessionId()); - } - this.getIndex(CLIENT_INDEX_PREFIX + session.getClientId()).remove(session.getSessionId()); - } - - /** 保存短时加密轮换结果。 */ - public void saveRotation(String oldTokenFingerprint, RefreshRotationResult result, - long ttlSeconds) { - RedisUtils.set(ROTATION_PREFIX + oldTokenFingerprint, JSONUtil.toJsonStr(result), - Duration.ofSeconds(Math.max(1, ttlSeconds))); - } - - /** 读取指定旧 Token 的短时轮换结果。 */ - public RefreshRotationResult getRotation(String oldTokenFingerprint) { - Object value = RedisUtils.get(ROTATION_PREFIX + oldTokenFingerprint); - return value == null ? null - : JSONUtil.toBean(value.toString(), RefreshRotationResult.class); - } - - /** 删除指定 Token 的短时轮换结果。 */ - public void deleteRotation(String tokenFingerprint) { - if (tokenFingerprint != null) { - RedisUtils.delete(ROTATION_PREFIX + tokenFingerprint); - } - } - - /** 保存 Session 在当前宽限期内的最新轮换结果。 */ - public void saveLatestRotation(String sessionId, RefreshRotationResult result, - long ttlSeconds) { - RedisUtils.set(LATEST_ROTATION_PREFIX + sessionId, JSONUtil.toJsonStr(result), - Duration.ofSeconds(Math.max(1, ttlSeconds))); - } - - /** 读取 Session 在当前宽限期内的最新轮换结果。 */ - public RefreshRotationResult getLatestRotation(String sessionId) { - Object value = RedisUtils.get(LATEST_ROTATION_PREFIX + sessionId); - return value == null ? null - : JSONUtil.toBean(value.toString(), RefreshRotationResult.class); - } - - /** 删除 Session 的最新轮换结果。 */ - public void deleteLatestRotation(String sessionId) { - RedisUtils.delete(LATEST_ROTATION_PREFIX + sessionId); - } - - /** 记录会话失效原因,供客户端下次请求读取更有针对性的提示。 */ - public void saveLogoutReason(String sessionId, LogoutReasonEnum reason) { - if (sessionId == null || reason == null) { - return; - } - RedisUtils.set(LOGOUT_REASON_PREFIX + sessionId, reason.name(), - Duration.ofSeconds(LOGOUT_REASON_TTL_SECONDS)); - } - - /** 读取会话失效原因;标记不存在或已过期时返回 null。 */ - public LogoutReasonEnum getLogoutReason(String sessionId) { - if (sessionId == null) { - return null; - } - Object value = RedisUtils.get(LOGOUT_REASON_PREFIX + sessionId); - if (value == null) { - return null; - } - try { - return LogoutReasonEnum.valueOf(value.toString()); - } catch (IllegalArgumentException e) { - return null; - } - } - - /** 删除会话失效原因标记。 */ - public void deleteLogoutReason(String sessionId) { - if (sessionId != null) { - RedisUtils.delete(LOGOUT_REASON_PREFIX + sessionId); - } - } - - /** 获取指定 Session 的分布式互斥锁。 */ - public RedisLockUtils lockSession(String sessionId) { - return this.acquireLock(SESSION_LOCK_PREFIX + sessionId); - } - - /** 获取用户登录策略锁,串行执行同一用户的新登录及 Session 数量控制。 */ - public AuthPolicyLock lockUserPolicy(Long userId) { - return this.acquirePolicyLock(redissonClient.getLock(USER_POLICY_LOCK_PREFIX + userId)); - } - - /** 获取租户安全策略读锁,不同用户可在同一租户内并发登录。 */ - public AuthPolicyLock lockTenantPolicyRead(Long tenantId) { - return this.acquirePolicyLock(redissonClient - .getReadWriteLock(TENANT_POLICY_LOCK_PREFIX + tenantId) - .readLock()); - } - - /** 获取租户安全策略写锁,与租户内的新登录严格串行。 */ - public AuthPolicyLock lockTenantPolicyWrite(Long tenantId) { - return this.acquirePolicyLock(redissonClient - .getReadWriteLock(TENANT_POLICY_LOCK_PREFIX + tenantId) - .writeLock()); - } - - /** 获取客户端安全策略读锁,不同用户可通过同一客户端并发登录。 */ - public AuthPolicyLock lockClientPolicyRead(String clientId) { - return this.acquirePolicyLock(redissonClient - .getReadWriteLock(CLIENT_POLICY_LOCK_PREFIX + clientId) - .readLock()); - } - - /** 获取客户端安全策略写锁,与该客户端的新登录严格串行。 */ - public AuthPolicyLock lockClientPolicyWrite(String clientId) { - return this.acquirePolicyLock(redissonClient - .getReadWriteLock(CLIENT_POLICY_LOCK_PREFIX + clientId) - .writeLock()); - } - - /** 尝试消耗一次指定维度的刷新配额。 */ - public boolean tryAcquireRateLimit(String key, int limit, Duration period) { - Long current = redissonClient.getScript(StringCodec.INSTANCE) - .eval(RScript.Mode.READ_WRITE, RATE_LIMIT_SCRIPT, RScript.ReturnType.INTEGER, - List.of(key), period.toMillis()); - return current != null && current <= limit; - } - - /** 读取创建新会话时需要固化的安全版本。 */ - public AuthSecurityVersion getSecurityVersion(Long userId, String clientId, Long tenantId) { - RBatch batch = redissonClient.createBatch(); - RFuture userVersion = batch.getAtomicLong(USER_VERSION_PREFIX + userId).getAsync(); - RFuture clientVersion = batch.getAtomicLong(CLIENT_VERSION_PREFIX + clientId) - .getAsync(); - RFuture tenantVersion = tenantId == null ? null - : batch.getAtomicLong(TENANT_VERSION_PREFIX + tenantId).getAsync(); - batch.execute(); - return new AuthSecurityVersion(this.awaitVersion(userVersion), - this.awaitVersion(clientVersion), - tenantVersion == null ? 0 : this.awaitVersion(tenantVersion)); - } - - /** 读取批量结果中的安全版本;批量已执行完成,读取不会阻塞。 */ - private long awaitVersion(RFuture version) { - return version.toCompletableFuture().join(); - } - - /** 判断 Session 固化的安全版本是否仍然有效。 */ - public boolean isSecurityVersionCurrent(RefreshSession session) { - AuthSecurityVersion current = this.getSecurityVersion(session.getUserId(), - session.getClientId(), session.getTenantId()); - return session.getUserSecurityVersion() == current.userVersion() - && session.getClientSecurityVersion() == current.clientVersion() - && session.getTenantSecurityVersion() == current.tenantVersion(); - } - - public void incrementUserSecurityVersion(Long userId) { - redissonClient.getAtomicLong(USER_VERSION_PREFIX + userId).incrementAndGet(); - } - - public void incrementTenantSecurityVersion(Long tenantId) { - redissonClient.getAtomicLong(TENANT_VERSION_PREFIX + tenantId).incrementAndGet(); - } - - public void incrementClientSecurityVersion(String clientId) { - redissonClient.getAtomicLong(CLIENT_VERSION_PREFIX + clientId).incrementAndGet(); - } - - private void addToIndex(String key, String sessionId, long expiresAt) { - RScoredSortedSet index = this.getIndex(key); - Instant expiration = Instant.ofEpochMilli(expiresAt); - // 先延长已有索引,避免旧 TTL 恰好在新增成员与设置 TTL 之间到期;新增 Key - // 再用 expireIfNotSet 补上 TTL,最后一次 GT 保证并发登录只能延长、不能缩短。 - index.expireIfGreater(expiration); - index.add(expiresAt, sessionId); - index.expireIfNotSet(expiration); - index.expireIfGreater(expiration); - } - - private Set findByIndex(String key) { - long now = System.currentTimeMillis(); - RScoredSortedSet index = this.getIndex(key); - index.removeRangeByScore(Double.NEGATIVE_INFINITY, true, now, true); - Collection values = index.valueRange(now, false, Double.POSITIVE_INFINITY, true); - return new LinkedHashSet<>(values); - } - - private RScoredSortedSet getIndex(String key) { - return redissonClient.getScoredSortedSet(key); - } - - private RedisLockUtils acquireLock(String key) { - RedisLockUtils lock = RedisLockUtils.tryLockWithWatchdog(key, LOCK_WAIT_MILLIS); - if (lock.isLocked()) { - return lock; - } - lock.close(); - throw RefreshTokenException.tooManyRequests("认证请求正在处理中,请稍后重试"); - } - - private AuthPolicyLock acquirePolicyLock(org.redisson.api.RLock lock) { - return AuthPolicyLock.acquire(lock, LOCK_WAIT_MILLIS); - } - - private long remainingSeconds(long expiresAt) { - long remainingMillis = expiresAt - System.currentTimeMillis(); - if (remainingMillis <= 0) { - return 1; - } - return remainingMillis / 1000 + (remainingMillis % 1000 == 0 ? 0 : 1); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenCodec.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenCodec.java deleted file mode 100644 index 69d499687..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenCodec.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.admin.auth.exception.RefreshTokenException; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; -import java.util.regex.Pattern; - -/** - * Refresh Token 编解码器。 - * - *

令牌格式固定为 {@code sessionId.secret}。sessionId 只负责定位服务端会话, - * secret 才是凭证;服务端只保存 secret 的 HMAC-SHA256 指纹。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Component -@RequiredArgsConstructor -public class RefreshTokenCodec { - - private static final int SESSION_ID_BYTES = 16; - private static final int SECRET_BYTES = 32; - private static final int GCM_IV_BYTES = 12; - private static final int GCM_TAG_BITS = 128; - private static final int MAX_TOKEN_LENGTH = 96; - private static final byte[] ENCRYPTION_AAD = - "continew-refresh-rotation-v1".getBytes(StandardCharsets.UTF_8); - private static final Pattern SESSION_ID_PATTERN = Pattern.compile("[A-Za-z0-9_-]{22}"); - private static final Pattern SECRET_PATTERN = Pattern.compile("[A-Za-z0-9_-]{43}"); - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - - private final RefreshTokenProperties properties; - - /** 生成 128 bit 随机会话 ID。 */ - public String newSessionId() { - return this.randomBase64Url(SESSION_ID_BYTES); - } - - /** 为指定 Session 生成 256 bit Refresh Token。 */ - public IssuedToken issue(String sessionId) { - if (!SESSION_ID_PATTERN.matcher(sessionId).matches()) { - throw new IllegalArgumentException("Refresh Session ID 格式无效"); - } - String secret = this.randomBase64Url(SECRET_BYTES); - return new IssuedToken(sessionId + "." + secret, sessionId, - this.fingerprint(secret)); - } - - /** 严格解析 Refresh Token,并计算 secret 指纹。 */ - public ParsedToken parse(String rawToken) { - if (rawToken == null || rawToken.isBlank() || rawToken.length() > MAX_TOKEN_LENGTH) { - throw this.invalidToken(); - } - int separator = rawToken.indexOf('.'); - if (separator <= 0 || separator != rawToken.lastIndexOf('.')) { - throw this.invalidToken(); - } - String sessionId = rawToken.substring(0, separator); - String secret = rawToken.substring(separator + 1); - if (!SESSION_ID_PATTERN.matcher(sessionId).matches() - || !SECRET_PATTERN.matcher(secret).matches()) { - throw this.invalidToken(); - } - return new ParsedToken(rawToken, sessionId, this.fingerprint(secret)); - } - - /** 对任意 bearer 凭证生成不可逆、带服务端密钥的稳定指纹。 */ - public String fingerprint(String credential) { - if (credential == null || credential.isBlank()) { - throw new IllegalArgumentException("凭证不能为空"); - } - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(this.deriveKey("fingerprint"), "HmacSHA256")); - byte[] digest = mac.doFinal(credential.getBytes(StandardCharsets.UTF_8)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } catch (GeneralSecurityException e) { - throw new IllegalStateException("无法计算 Refresh Token 指纹", e); - } - } - - /** 使用常量时间比较两个指纹。 */ - public boolean matches(String actualFingerprint, String expectedFingerprint) { - if (actualFingerprint == null || expectedFingerprint == null) { - return false; - } - return MessageDigest.isEqual(actualFingerprint.getBytes(StandardCharsets.US_ASCII), - expectedFingerprint.getBytes(StandardCharsets.US_ASCII)); - } - - /** 使用 AES-256-GCM 加密短时轮换快照。 */ - public String encrypt(String value) { - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("待加密凭证不能为空"); - } - try { - byte[] iv = new byte[GCM_IV_BYTES]; - SECURE_RANDOM.nextBytes(iv); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.ENCRYPT_MODE, - new SecretKeySpec(this.deriveKey("encryption"), "AES"), - new GCMParameterSpec(GCM_TAG_BITS, iv)); - cipher.updateAAD(ENCRYPTION_AAD); - byte[] encrypted = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8)); - return Base64.getUrlEncoder().withoutPadding() - .encodeToString(ByteBuffer.allocate(iv.length + encrypted.length) - .put(iv) - .put(encrypted) - .array()); - } catch (GeneralSecurityException e) { - throw new IllegalStateException("无法加密 Refresh Token 轮换快照", e); - } - } - - /** 解密并校验 AES-GCM 轮换快照。 */ - public String decrypt(String value) { - if (value == null || value.isBlank()) { - throw this.invalidToken(); - } - try { - byte[] payload = Base64.getUrlDecoder().decode(value); - if (payload.length <= GCM_IV_BYTES) { - throw this.invalidToken(); - } - byte[] iv = new byte[GCM_IV_BYTES]; - byte[] encrypted = new byte[payload.length - GCM_IV_BYTES]; - System.arraycopy(payload, 0, iv, 0, iv.length); - System.arraycopy(payload, iv.length, encrypted, 0, encrypted.length); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, - new SecretKeySpec(this.deriveKey("encryption"), "AES"), - new GCMParameterSpec(GCM_TAG_BITS, iv)); - cipher.updateAAD(ENCRYPTION_AAD); - return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8); - } catch (GeneralSecurityException | IllegalArgumentException e) { - throw this.invalidToken(); - } - } - - private byte[] deriveKey(String purpose) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(("continew-refresh-" + purpose + "\0") - .getBytes(StandardCharsets.UTF_8)); - return digest.digest(properties.getSecret().getBytes(StandardCharsets.UTF_8)); - } catch (GeneralSecurityException e) { - throw new IllegalStateException("无法派生 Refresh Token 密钥", e); - } - } - - private String randomBase64Url(int byteLength) { - byte[] bytes = new byte[byteLength]; - SECURE_RANDOM.nextBytes(bytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - } - - private RefreshTokenException invalidToken() { - return RefreshTokenException.unauthorized("登录状态已失效,请重新登录"); - } - - /** 新签发的 Refresh Token。 */ - public record IssuedToken(String rawToken, String sessionId, String fingerprint) { - } - - /** 已解析的 Refresh Token;不向调用方暴露 secret。 */ - public record ParsedToken(String rawToken, String sessionId, String fingerprint) { - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenRequestGuard.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenRequestGuard.java deleted file mode 100644 index 137ff6529..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/RefreshTokenRequestGuard.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import cn.hutool.core.util.StrUtil; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseCookie; -import org.springframework.stereotype.Component; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.exception.RefreshTokenException; - -import java.net.URI; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; - -/** - * Refresh Token HTTP 请求守卫。 - * - *

集中负责 Cookie/BODY 传输边界、Cookie 来源校验、刷新限流和禁止缓存响应, - * 避免这些安全规则散落在令牌轮换流程中。

- * - * @author luoqiz - */ -@Component -public class RefreshTokenRequestGuard { - - private static final String RATE_LIMIT_PREFIX = "AUTH:REFRESH:V1:RATE_LIMIT:"; - - private final RefreshTokenProperties properties; - private final RefreshTokenCodec tokenCodec; - private final RefreshSessionStore sessionStore; - private final List cookieAllowedOriginMatchers; - - public RefreshTokenRequestGuard(RefreshTokenProperties properties, RefreshTokenCodec tokenCodec, - RefreshSessionStore sessionStore) { - this.properties = properties; - this.tokenCodec = tokenCodec; - this.sessionStore = sessionStore; - this.cookieAllowedOriginMatchers = properties.getCookieAllowedOrigins().stream() - .map(this::compileAllowedOriginMatcher) - .toList(); - } - - /** 从 Cookie 或 BODY 中读取 Refresh Token;同时出现两种来源时拒绝请求。 */ - public String resolve(String bodyRefreshToken, HttpServletRequest request) { - String bodyToken = StrUtil.trim(bodyRefreshToken); - String cookieToken = this.readCookie(request); - if (StrUtil.isNotBlank(bodyToken) && StrUtil.isNotBlank(cookieToken)) { - throw RefreshTokenException.forbidden("Refresh Token 来源冲突,请重新登录"); - } - return StrUtil.isNotBlank(bodyToken) ? bodyToken : cookieToken; - } - - /** 在解析不可信 Token 之前按客户端地址执行刷新限流。 */ - public void checkRequestRateLimit(HttpServletRequest request) { - int ipLimit = properties.getIpRateLimit(); - if (ipLimit <= 0) { - return; - } - String clientIp = request == null ? "unknown" : this.resolveClientIp(request); - clientIp = StrUtil.blankToDefault(clientIp, "unknown"); - this.requireRateLimit("IP:" + tokenCodec.fingerprint(clientIp), ipLimit); - } - - /** 对真正产生新令牌的一次 Session 轮换执行限流。 */ - public void checkSessionRateLimit(String sessionId) { - this.requireRateLimit("SESSION:" + sessionId, properties.getSessionRateLimit(), - Duration.ofSeconds(properties.getSessionRateLimitPeriod())); - } - - /** 校验已识别 Refresh Session 的令牌传输方式和 Cookie 请求来源。 */ - public void validateRequest(String rawRefreshToken, HttpServletRequest request) { - if (StrUtil.isBlank(rawRefreshToken) || request == null) { - return; - } - RefreshTokenCodec.ParsedToken token = tokenCodec.parse(rawRefreshToken); - RefreshSession session = sessionStore.get(token.sessionId()); - // 已轮换多次的旧 Token 可能不再是 current/previous,但仍可在极短宽限期内命中 - // 幂等快照。此类请求同样必须执行 Cookie 来源校验,不能因走重放快照而绕过 CSRF。 - if (session == null || !this.isKnownToken(session, token.fingerprint()) - && sessionStore.getRotation(token.fingerprint()) == null) { - return; - } - String cookieToken = this.readCookie(request); - if (RefreshTokenModeEnum.COOKIE.equals(session.getMode())) { - if (!Objects.equals(rawRefreshToken, cookieToken) || !this.isTrustedOrigin(request)) { - throw RefreshTokenException.forbidden("请求来源不合法,请重新登录"); - } - return; - } - if (StrUtil.isNotBlank(cookieToken)) { - throw RefreshTokenException.forbidden("Refresh Token 传输方式不合法,请重新登录"); - } - } - - /** 请求携带 Refresh Token Cookie 时,在解析 Cookie 前校验请求来源。 */ - public void validateCookieOrigin(HttpServletRequest request) { - if (this.hasRefreshTokenCookie(request) && !this.isTrustedOrigin(request)) { - throw RefreshTokenException.forbidden("请求来源不合法,请重新登录"); - } - } - - /** 写入浏览器 HttpOnly Refresh Token Cookie。 */ - public void writeCookie(HttpServletResponse response, String rawToken, long ttlSeconds) { - ResponseCookie cookie = ResponseCookie.from(properties.getCookieName(), rawToken) - .httpOnly(true) - .secure(properties.isCookieSecure()) - .sameSite(properties.getCookieSameSite()) - .path(properties.getCookiePath()) - .maxAge(Duration.ofSeconds(ttlSeconds)) - .build(); - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - } - - /** 清理浏览器 Refresh Token Cookie。 */ - public void clearCookie(HttpServletResponse response) { - this.disableCaching(response); - ResponseCookie cookie = ResponseCookie.from(properties.getCookieName(), "") - .httpOnly(true) - .secure(properties.isCookieSecure()) - .sameSite(properties.getCookieSameSite()) - .path(properties.getCookiePath()) - .maxAge(Duration.ZERO) - .build(); - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - } - - /** 禁止浏览器和中间代理缓存认证响应。 */ - public void disableCaching(HttpServletResponse response) { - response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store"); - response.setHeader(HttpHeaders.PRAGMA, "no-cache"); - } - - private String readCookie(HttpServletRequest request) { - if (request == null || request.getCookies() == null) { - return null; - } - String value = null; - for (Cookie cookie : request.getCookies()) { - if (!properties.getCookieName().equals(cookie.getName())) { - continue; - } - if (value != null) { - throw RefreshTokenException.forbidden("Refresh Token Cookie 重复,请重新登录"); - } - value = cookie.getValue(); - } - return value; - } - - private boolean hasRefreshTokenCookie(HttpServletRequest request) { - if (request == null || request.getCookies() == null) { - return false; - } - for (Cookie cookie : request.getCookies()) { - if (properties.getCookieName().equals(cookie.getName())) { - return true; - } - } - return false; - } - - private boolean isKnownToken(RefreshSession session, String fingerprint) { - return tokenCodec.matches(fingerprint, session.getCurrentTokenFingerprint()) - || tokenCodec.matches(fingerprint, session.getPreviousTokenFingerprint()); - } - - private void requireRateLimit(String keySuffix, int limit) { - this.requireRateLimit(keySuffix, limit, - Duration.ofSeconds(properties.getIpRateLimitPeriod())); - } - - private void requireRateLimit(String keySuffix, int limit, Duration period) { - boolean allowed = sessionStore.tryAcquireRateLimit(RATE_LIMIT_PREFIX + keySuffix, limit, - period); - if (!allowed) { - throw RefreshTokenException.tooManyRequests("刷新请求过于频繁,请稍后重试"); - } - } - - private boolean isTrustedOrigin(HttpServletRequest request) { - String origin = request.getHeader(HttpHeaders.ORIGIN); - if (StrUtil.isNotBlank(origin)) { - return this.isAllowedOrigin(origin, request); - } - String referer = request.getHeader(HttpHeaders.REFERER); - if (StrUtil.isBlank(referer)) { - return false; - } - try { - URI refererUri = URI.create(referer); - String scheme = refererUri.getScheme(); - String host = refererUri.getHost(); - if (StrUtil.isBlank(scheme) || StrUtil.isBlank(host) - || !"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { - return false; - } - int port = refererUri.getPort(); - String refererOrigin = scheme + "://" + host + (port < 0 ? "" : ":" + port); - return this.isAllowedOrigin(refererOrigin, request); - } catch (IllegalArgumentException e) { - return false; - } - } - - private String resolveClientIp(HttpServletRequest request) { - String remoteAddress = StrUtil.blankToDefault(request.getRemoteAddr(), "unknown"); - int trustedProxyHops = properties.getTrustedProxyHops(); - if (trustedProxyHops <= 0 - || !properties.getTrustedProxyAddresses().contains(remoteAddress)) { - return remoteAddress; - } - String forwardedFor = request.getHeader("X-Forwarded-For"); - if (StrUtil.isBlank(forwardedFor)) { - return remoteAddress; - } - List addresses = new ArrayList<>(); - for (String address : StrUtil.splitTrim(forwardedFor, ',')) { - if (StrUtil.isNotBlank(address)) { - addresses.add(address); - } - } - int clientIndex = addresses.size() - trustedProxyHops; - return clientIndex < 0 ? remoteAddress : addresses.get(clientIndex); - } - - private boolean isAllowedOrigin(String origin, HttpServletRequest request) { - URI originUri = this.parseOrigin(origin); - if (originUri == null) { - return false; - } - String normalizedOrigin = this.normalizeOrigin(originUri); - if (cookieAllowedOriginMatchers.stream() - .anyMatch(pattern -> pattern.matcher(normalizedOrigin).matches())) { - return true; - } - return request.getScheme().equalsIgnoreCase(originUri.getScheme()) - && request.getServerName().equalsIgnoreCase(originUri.getHost()) - && this.normalizePort(request.getScheme(), request.getServerPort()) == this - .normalizePort(originUri.getScheme(), originUri.getPort()); - } - - private URI parseOrigin(String value) { - // 与 RefreshTokenProperties.isValidOrigin 共用同一份语义,避免两份校验漂移。 - if (StrUtil.isBlank(value) || !RefreshTokenProperties.isValidOrigin(value)) { - return null; - } - try { - return URI.create(value); - } catch (IllegalArgumentException e) { - return null; - } - } - - private Pattern compileAllowedOriginMatcher(String allowedOrigin) { - URI allowedUri = this.parseOrigin(allowedOrigin); - if (allowedUri != null) { - return Pattern.compile(Pattern.quote(this.normalizeOrigin(allowedUri)), - Pattern.CASE_INSENSITIVE); - } - int wildcardIndex = allowedOrigin.indexOf('*'); - String expression = Pattern.quote(allowedOrigin.substring(0, wildcardIndex)) - + "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?" - + Pattern.quote(allowedOrigin.substring(wildcardIndex + 1)); - return Pattern.compile(expression, Pattern.CASE_INSENSITIVE); - } - - private String normalizeOrigin(URI uri) { - String scheme = uri.getScheme(); - int port = this.normalizePort(scheme, uri.getPort()); - boolean defaultPort = "https".equalsIgnoreCase(scheme) ? port == 443 : port == 80; - return scheme + "://" + uri.getHost() + (defaultPort ? "" : ":" + port); - } - - private int normalizePort(String scheme, int port) { - if (port >= 0) { - return port; - } - return "https".equalsIgnoreCase(scheme) ? 443 : 80; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/TenantArgumentPolicyLockTargetResolver.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/TenantArgumentPolicyLockTargetResolver.java deleted file mode 100644 index cb783b140..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/TenantArgumentPolicyLockTargetResolver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; - -import java.util.Collection; -import java.util.List; - -/** 根据约定的业务方法参数解析租户策略锁目标。 */ -@Component -public class TenantArgumentPolicyLockTargetResolver implements AuthPolicyLockTargetResolver { - - @Override - public Collection resolve(Object[] args) { - for (Object value : args) { - if (value instanceof Long tenantId) { - return List.of(AuthPolicyLockTarget.tenant(tenantId)); - } - if (value instanceof Collection values) { - List targets = values.stream().filter(Long.class::isInstance) - .map(Long.class::cast).map(AuthPolicyLockTarget::tenant).toList(); - if (!targets.isEmpty()) { - return targets; - } - } - } - throw new IllegalArgumentException("认证租户策略锁参数无效"); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolver.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolver.java deleted file mode 100644 index add9acd5b..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; - -import java.util.Collection; -import java.util.List; - -/** 根据约定的业务方法参数解析用户策略锁目标。 */ -@Component -public class UserArgumentPolicyLockTargetResolver implements AuthPolicyLockTargetResolver { - - @Override - public Collection resolve(Object[] args) { - for (Object value : args) { - if (value instanceof Long userId) { - return List.of(AuthPolicyLockTarget.user(userId)); - } - if (value instanceof Collection values) { - List targets = values.stream().filter(Long.class::isInstance) - .map(Long.class::cast).map(AuthPolicyLockTarget::user).toList(); - if (!targets.isEmpty()) { - return targets; - } - } - } - throw new IllegalArgumentException("认证用户策略锁参数无效"); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/AuthWebSocketSessionService.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/AuthWebSocketSessionService.java deleted file mode 100644 index d533a7d24..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/AuthWebSocketSessionService.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.convert.Convert; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.redisson.api.RTopic; -import org.redisson.api.RedissonClient; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.stereotype.Service; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketSession; -import top.continew.admin.auth.api.AuthSessionConstants; -import top.continew.admin.auth.api.AccessSessionValidator; -import top.continew.admin.auth.api.AuthSessionRevocationNotifier; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao; - -import java.io.IOException; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Refresh Session 与 WebSocket 连接联动服务。 - * - *

Starter 使用 Access Token 作为 WebSocket 客户端 ID。本服务通过 Access Token 中的 - * {@code sid} 找到同一 Refresh Session 的连接,并使用 Redis Topic 通知所有应用实例, - * 避免用户禁用、强退或密码修改后已建立的连接继续存活。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class AuthWebSocketSessionService implements AuthSessionRevocationNotifier { - - private static final String REVOCATION_TOPIC = "AUTH:REFRESH:V1:WEBSOCKET:REVOKED"; - - private final RedissonClient redissonClient; - private final ObjectProvider sessionDaoProvider; - private final ObjectProvider accessSessionValidatorProvider; - private final RefreshTokenProperties refreshTokenProperties; - - /** 本实例 Access Token → Refresh Session ID 的本地索引,避免每次撤销通知回源 Redis。 */ - private final Map tokenSessionIdCache = new ConcurrentHashMap<>(); - - private RTopic revocationTopic; - private Integer listenerId; - - /** 订阅集群内 Refresh Session 撤销通知。 */ - @PostConstruct - public void subscribe() { - revocationTopic = redissonClient.getTopic(REVOCATION_TOPIC); - listenerId = revocationTopic.addListener(String.class, (channel, sessionId) -> { - try { - this.closeLocal(sessionId); - } catch (RuntimeException e) { - // 与 notifyRevoked 的降级策略一致:DAO/Redis 抖动时不阻断监听器, - // 本次撤销消息丢失由周期校验与本地缓存 TTL 兜底。 - log.warn("收到撤销通知后关闭会话 [{}] 的本地 WebSocket 连接失败", sessionId, e); - } - }); - } - - /** 释放 Redis Topic 监听器。 */ - @PreDestroy - public void unsubscribe() { - if (revocationTopic != null && listenerId != null) { - revocationTopic.removeListener(listenerId); - } - } - - /** - * 关闭本实例连接并通知集群内其他实例关闭同一登录会话的连接。 - * - * @param sessionId Refresh Session ID - */ - @Override - public void notifyRevoked(String sessionId) { - if (sessionId == null || sessionId.isBlank()) { - return; - } - try { - revocationTopic.publish(sessionId); - } catch (RuntimeException e) { - // Refresh Session 已经由认证服务删除,实时通知只是加速关闭连接的旁路机制。 - log.warn("发布 Refresh Session [{}] 的 WebSocket 撤销通知失败", sessionId, e); - } - try { - this.closeLocal(sessionId); - } catch (RuntimeException e) { - log.warn("关闭 Refresh Session [{}] 的本地 WebSocket 连接失败", sessionId, e); - } - } - - /** - * 周期校验本实例的 WebSocket 凭证,兜底 Redis Pub/Sub 丢消息和 Token 自然过期。 - */ - @Scheduled(fixedDelayString = "#{@refreshTokenProperties.websocketValidationInterval}") - public void validateLocalSessions() { - WebSocketSessionDao sessionDao = sessionDaoProvider.getIfAvailable(); - AccessSessionValidator accessSessionValidator = - accessSessionValidatorProvider.getIfAvailable(); - if (sessionDao == null || accessSessionValidator == null) { - return; - } - for (String accessToken : new LinkedHashSet<>(sessionDao.listAllSessionIds())) { - try { - if (accessSessionValidator.isInvalid(accessToken)) { - this.closeLocalAccessToken(sessionDao, accessToken); - this.tokenSessionIdCache.remove(accessToken); - } - } catch (RuntimeException e) { - // 基础设施短暂异常时保留连接,下一轮继续校验,避免误杀全部实时连接。 - log.warn("校验 WebSocket Access Token 所属登录会话失败", e); - } - } - // 连接已关闭的 Token 不再保留本地索引条目,避免缓存无限增长。 - Set currentTokens = new LinkedHashSet<>(sessionDao.listAllSessionIds()); - this.tokenSessionIdCache.keySet().removeIf(token -> !currentTokens.contains(token)); - } - - private void closeLocal(String sessionId) { - WebSocketSessionDao sessionDao = sessionDaoProvider.getIfAvailable(); - if (sessionDao == null) { - return; - } - // DAO 的 Key 是握手时保存的 Access Token。复制一份,避免关闭回调同步删除时 - // 修改正在遍历的集合。 - Set tokens = new LinkedHashSet<>(sessionDao.listAllSessionIds()); - for (String accessToken : tokens) { - if (!this.belongsToSession(accessToken, sessionId)) { - continue; - } - this.closeLocalAccessToken(sessionDao, accessToken); - this.tokenSessionIdCache.remove(accessToken); - } - // 连接已关闭的 Token 不再保留本地索引条目,避免缓存无限增长。 - this.tokenSessionIdCache.keySet().removeIf(token -> !tokens.contains(token)); - } - - private void closeLocalAccessToken(WebSocketSessionDao sessionDao, String accessToken) { - if (sessionDao instanceof MultiWebSocketSessionDao multiSessionDao) { - // 浏览器多标签页共用同一 Access Token 时,关闭该 Token 的全部连接。 - for (WebSocketSession session : multiSessionDao.listByKey(accessToken)) { - this.closeSession(session); - } - multiSessionDao.removeAll(accessToken); - return; - } - this.closeSession(sessionDao.get(accessToken)); - sessionDao.delete(accessToken); - } - - private void closeSession(WebSocketSession webSocketSession) { - if (webSocketSession == null) { - return; - } - try { - if (webSocketSession.isOpen()) { - webSocketSession.close(CloseStatus.POLICY_VIOLATION); - } - } catch (IOException e) { - log.warn("关闭失效认证会话的 WebSocket 连接失败", e); - } - } - - private boolean belongsToSession(String accessToken, String sessionId) { - String cachedSessionId = tokenSessionIdCache.get(accessToken); - if (cachedSessionId != null) { - return Objects.equals(sessionId, cachedSessionId); - } - try { - String claimedSessionId = Convert.toStr(StpUtil.getExtra(accessToken, - AuthSessionConstants.SESSION_ID_CLAIM)); - // Access Token 的会话声明在签发后不可变,可以安全缓存,批量撤销时 - // 只需首次回源 Redis,后续通知全部命中本地索引。 - if (claimedSessionId != null) { - tokenSessionIdCache.put(accessToken, claimedSessionId); - } - return Objects.equals(sessionId, claimedSessionId); - } catch (RuntimeException e) { - log.debug("忽略无法解析会话声明的 WebSocket Access Token", e); - return false; - } - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/ConcurrentWebSocketSessionDao.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/ConcurrentWebSocketSessionDao.java deleted file mode 100644 index fa1a5e723..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/ConcurrentWebSocketSessionDao.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import org.springframework.web.socket.WebSocketSession; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 支持多标签页的 WebSocket 会话 DAO 内存实现。 - * - *

以 {@code Key → (连接 ID → 连接)} 两级登记:同一 Access Token 的多个标签页 - * 连接互不覆盖。Starter 的 {@code afterConnectionClosed}/{@code handleTransportError} - * 关闭回调只携带 Key,因此 {@link #delete(String)} 采用「只移除已关闭连接」的语义, - * 保留同一 Key 下仍存活的其它标签页连接。

- * - * @author luoqiz - * @since 4.2.0 - */ -public class ConcurrentWebSocketSessionDao implements MultiWebSocketSessionDao { - - private final Map> sessions = new ConcurrentHashMap<>(); - - @Override - public void add(String key, WebSocketSession session) { - sessions.computeIfAbsent(key, k -> new ConcurrentHashMap<>()) - .put(session.getId(), session); - } - - @Override - public void delete(String key) { - Map byId = sessions.get(key); - if (byId == null) { - return; - } - byId.values().removeIf(session -> !session.isOpen()); - if (byId.isEmpty()) { - sessions.remove(key); - } - } - - @Override - public WebSocketSession get(String key) { - Map byId = sessions.get(key); - if (byId == null || byId.isEmpty()) { - return null; - } - // 推送是单接收方语义(WebSocketUtils.sendMessage),取最新一条存活连接。 - WebSocketSession latest = null; - for (WebSocketSession session : byId.values()) { - if (session.isOpen()) { - latest = session; - } - } - return latest; - } - - @Override - public Collection listAll() { - return sessions.values().stream().flatMap(byId -> byId.values().stream()).toList(); - } - - @Override - public Set listAllSessionIds() { - return sessions.keySet(); - } - - @Override - public Collection listByKey(String key) { - Map byId = sessions.get(key); - return byId == null ? List.of() : List.copyOf(byId.values()); - } - - @Override - public void removeAll(String key) { - sessions.remove(key); - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/MultiWebSocketSessionDao.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/MultiWebSocketSessionDao.java deleted file mode 100644 index 0bbf7926d..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/MultiWebSocketSessionDao.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import org.springframework.web.socket.WebSocketSession; -import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao; - -import java.util.Collection; - -/** - * 支持同一客户端 Key 挂载多条连接的 WebSocket 会话 DAO。 - * - *

浏览器多标签页共用同一 Access Token 时,Starter 默认 DAO 以 Key 为唯一维度、 - * 后建连接覆盖前者;本接口补充按 Key 枚举与整组删除能力,供撤销路径全量关闭。

- * - * @author luoqiz - * @since 4.2.0 - */ -public interface MultiWebSocketSessionDao extends WebSocketSessionDao { - - /** - * 获取指定 Key 挂载的全部连接。 - * - * @param key 客户端 Key(Access Token) - * @return 连接集合(可能为空) - */ - Collection listByKey(String key); - - /** - * 删除指定 Key 的全部连接登记。 - * - * @param key 客户端 Key(Access Token) - */ - void removeAll(String key); -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketClientServiceImpl.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketClientServiceImpl.java deleted file mode 100644 index 7e898c421..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketClientServiceImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import cn.dev33.satoken.stp.StpUtil; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.stereotype.Service; -import top.continew.admin.auth.api.AccessSessionValidator; -import top.continew.starter.core.exception.BusinessException; -import top.continew.starter.messaging.websocket.core.WebSocketClientService; - -/** - * 当前登录用户 Provider - * - * @author Charles7c - * @since 2024/6/4 22:13 - */ -@Service -@RequiredArgsConstructor -public class WebSocketClientServiceImpl implements WebSocketClientService { - - private final AccessSessionValidator accessSessionValidator; - - @Override - public String getClientId(ServletServerHttpRequest request) { - HttpServletRequest servletRequest = request.getServletRequest(); - String token = servletRequest.getParameter("token"); - if (token == null || token.isBlank() || StpUtil.getLoginIdByToken(token) == null - || accessSessionValidator.isInvalid(token)) { - throw new BusinessException("登录已过期,请重新登录"); - } - return token; - } -} diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketSessionDaoConfiguration.java b/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketSessionDaoConfiguration.java deleted file mode 100644 index a4f0a0dd9..000000000 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/websocket/WebSocketSessionDaoConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao; - -/** - * WebSocket 会话 DAO 配置。 - * - *

用多标签页实现替换 Starter 的 {@code @ConditionalOnMissingBean} 默认 DAO: - * 同一 Access Token 的多条连接互不覆盖,撤销时可全量关闭。

- * - * @author luoqiz - * @since 4.2.0 - */ -@Configuration -public class WebSocketSessionDaoConfiguration { - - @Bean - public WebSocketSessionDao webSocketSessionDao() { - return new ConcurrentWebSocketSessionDao(); - } -} diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/service/RefreshTokenServiceImplTest.java b/continew-auth-refresh/src/test/java/top/continew/admin/auth/service/RefreshTokenServiceImplTest.java deleted file mode 100644 index 2cff6e35e..000000000 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/service/RefreshTokenServiceImplTest.java +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.service; - -import cn.dev33.satoken.stp.StpUtil; -import jakarta.servlet.http.Cookie; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; -import org.mockito.MockedStatic; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.admin.auth.enums.LogoutReasonEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; -import top.continew.admin.auth.model.AuthSecurityVersion; -import top.continew.admin.auth.model.RefreshClientPolicy; -import top.continew.admin.auth.model.RefreshRotationResult; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.resp.LoginResp; -import top.continew.admin.auth.api.AuthSessionConstants; -import top.continew.admin.auth.enums.SessionReplacementScope; -import top.continew.admin.auth.service.impl.RefreshTokenServiceImpl; -import top.continew.admin.auth.service.RefreshTokenService.LoginAttempt; -import top.continew.admin.auth.support.AccessSessionCache; -import top.continew.admin.auth.support.RefreshSessionStore; -import top.continew.admin.auth.support.RefreshTokenCodec; -import top.continew.admin.auth.support.RefreshTokenRequestGuard; -import top.continew.admin.auth.support.AuthPolicyLock; -import top.continew.admin.auth.support.RefreshTokenCodec.IssuedToken; -import top.continew.admin.auth.api.AuthSessionRevocationNotifier; -import top.continew.admin.auth.exception.RefreshTokenException; -import top.continew.starter.cache.redisson.util.RedisLockUtils; -import top.continew.starter.core.exception.BusinessException; - -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** Refresh Token 轮换、重放和传输边界测试。 */ -class RefreshTokenServiceImplTest { - - private final Map rotations = new HashMap<>(); - private final Map latestRotations = new HashMap<>(); - private RefreshSessionStore sessionStore; - private RefreshTokenProperties properties; - private RefreshTokenCodec codec; - private RefreshTokenServiceImpl service; - private AuthSessionRevocationNotifier sessionRevocationNotifier; - private AccessSessionCache accessSessionCache; - private RefreshSession session; - private IssuedToken initialToken; - - @BeforeEach - void setUp() { - properties = new RefreshTokenProperties(); - properties.setSecret("test-only-refresh-token-secret-with-32-bytes"); - properties.setIpRateLimit(0); - properties.setSessionRateLimit(10); - properties.setRotationGracePeriod(5); - codec = new RefreshTokenCodec(properties); - - sessionStore = mock(RefreshSessionStore.class); - RedisLockUtils sessionLock = mock(RedisLockUtils.class); - AuthPolicyLock policyLock = mock(AuthPolicyLock.class); - when(sessionStore.lockSession(anyString())).thenReturn(sessionLock); - when(sessionStore.lockUserPolicy(anyLong())).thenReturn(policyLock); - when(sessionStore.lockTenantPolicyRead(anyLong())).thenReturn(policyLock); - when(sessionStore.lockTenantPolicyWrite(anyLong())).thenReturn(policyLock); - when(sessionStore.lockClientPolicyRead(anyString())).thenReturn(policyLock); - when(sessionStore.lockClientPolicyWrite(anyString())).thenReturn(policyLock); - when(sessionStore.tryAcquireRateLimit(anyString(), anyInt(), any(Duration.class))) - .thenReturn(true); - when(sessionStore.getSecurityVersion(anyLong(), anyString(), anyLong())) - .thenReturn(new AuthSecurityVersion(0, 0, 0)); - when(sessionStore.isSecurityVersionCurrent(any(RefreshSession.class))).thenReturn(true); - when(sessionStore.getRotation(anyString())).thenAnswer(invocation -> rotations - .get(invocation.getArgument(0, String.class))); - org.mockito.Mockito.doAnswer(invocation -> { - rotations.put(invocation.getArgument(0, String.class), - invocation.getArgument(1, RefreshRotationResult.class)); - return null; - }).when(sessionStore).saveRotation(anyString(), any(RefreshRotationResult.class), - anyLong()); - when(sessionStore.getLatestRotation(anyString())).thenAnswer(invocation -> latestRotations - .get(invocation.getArgument(0, String.class))); - org.mockito.Mockito.doAnswer(invocation -> { - latestRotations.put(invocation.getArgument(0, String.class), - invocation.getArgument(1, RefreshRotationResult.class)); - return null; - }).when(sessionStore).saveLatestRotation(anyString(), any(RefreshRotationResult.class), - anyLong()); - - initialToken = codec.issue(codec.newSessionId()); - session = new RefreshSession(); - session.setSessionId(initialToken.sessionId()); - session.setUserId(1L); - session.setClientId("web"); - session.setMode(RefreshTokenModeEnum.BODY); - session.setExpiresAt(System.currentTimeMillis() + Duration.ofDays(1).toMillis()); - session.setCurrentTokenFingerprint(initialToken.fingerprint()); - when(sessionStore.get(initialToken.sessionId())).thenAnswer(invocation -> session); - - sessionRevocationNotifier = mock(AuthSessionRevocationNotifier.class); - accessSessionCache = mock(AccessSessionCache.class); - RefreshTokenRequestGuard requestGuard = new RefreshTokenRequestGuard(properties, codec, - sessionStore); - service = new RefreshTokenServiceImpl(properties, codec, sessionStore, requestGuard, - sessionRevocationNotifier, accessSessionCache); - } - - @Test - void shouldRotateAndReplayExactlyTheSameResult() { - LoginResp first = service.rotate(initialToken.rawToken(), response(), - issuer("access-1")); - - assertEquals("access-1", first.getAccessToken()); - assertEquals(initialToken.sessionId(), codec.parse(first.getRefreshToken()).sessionId()); - assertNotEquals(initialToken.rawToken(), first.getRefreshToken()); - assertEquals(initialToken.fingerprint(), session.getPreviousTokenFingerprint()); - assertTrue(codec.matches(codec.parse(first.getRefreshToken()).fingerprint(), - session.getCurrentTokenFingerprint())); - LoginResp replay = - service.rotate(initialToken.rawToken(), response(), ignored -> { - throw new AssertionError("幂等重放不应再次签发 Access Token"); - }); - assertEquals(first.getAccessToken(), replay.getAccessToken()); - assertEquals(first.getRefreshToken(), replay.getRefreshToken()); - verify(sessionStore, times(1)).tryAcquireRateLimit(anyString(), anyInt(), - any(Duration.class)); - } - - @Test - void shouldAcquirePolicyLocksBeforeSessionLockWhenRotating() { - session.setTenantId(2L); - - service.rotate(initialToken.rawToken(), response(), issuer("access-1")); - - InOrder order = inOrder(sessionStore); - order.verify(sessionStore).lockUserPolicy(1L); - order.verify(sessionStore).lockTenantPolicyRead(2L); - order.verify(sessionStore).lockClientPolicyRead("web"); - order.verify(sessionStore).lockSession(initialToken.sessionId()); - } - - @Test - void shouldNotAdvanceAnotherGenerationInsideGracePeriod() { - LoginResp first = service.rotate(initialToken.rawToken(), response(), issuer("access-1")); - - LoginResp replay = service.rotate(first.getRefreshToken(), response(), ignored -> { - throw new AssertionError("宽限期内不应再次签发 Access Token"); - }); - - assertEquals(first.getAccessToken(), replay.getAccessToken()); - assertEquals(first.getRefreshToken(), replay.getRefreshToken()); - verify(sessionStore, times(1)).tryAcquireRateLimit(anyString(), anyInt(), - any(Duration.class)); - } - - @Test - void shouldNotRevokeSessionForUnknownSecret() { - IssuedToken unknown = codec.issue(initialToken.sessionId()); - - assertThrows(BusinessException.class, - () -> service.rotate(unknown.rawToken(), response(), issuer("unused"))); - - verify(sessionStore, never()).lockSession(anyString()); - verify(sessionStore, never()).save(session); - verify(sessionStore, never()).delete(session.getSessionId()); - verify(sessionStore, never()).removeIndexes(session); - } - - @Test - void shouldRejectOldGenerationTokenWhenSessionAlreadyRevoked() { - // R0→R1→R2 后会话被撤销(强退/顶人),R0 指纹的轮换快照仍在宽限期内残留。 - // 凭证已无对应会话,必须按无效令牌处理,不能进入候选会话空指针。 - rotations.put(initialToken.fingerprint(), new RefreshRotationResult()); - when(sessionStore.get(initialToken.sessionId())).thenReturn(null); - - RefreshTokenException exception = assertThrows(RefreshTokenException.class, - () -> service.rotate(initialToken.rawToken(), response(), issuer("unused"))); - - assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatus()); - verify(sessionStore, never()).lockSession(anyString()); - verify(sessionStore, never()).lockUserPolicy(anyLong()); - } - - @Test - void shouldRevokePreviousTokenWhenGraceSnapshotIsGone() { - service.rotate(initialToken.rawToken(), response(), issuer("access-1")); - rotations.clear(); - latestRotations.clear(); - // 超出宽限期的明确重放必须直接撤销,不能被已耗尽的刷新配额挡住。 - when(sessionStore.tryAcquireRateLimit(anyString(), anyInt(), any(Duration.class))) - .thenReturn(false); - - assertThrows(BusinessException.class, - () -> service.rotate(initialToken.rawToken(), response(), issuer("unused"))); - - verify(sessionStore).delete(session.getSessionId()); - verify(sessionStore).removeIndexes(session); - verify(sessionStore, times(1)).tryAcquireRateLimit(anyString(), anyInt(), - any(Duration.class)); - } - - @Test - void shouldPreserveSessionAndResumeSameRotationAfterTemporaryFailure() { - RuntimeException failure = new RuntimeException("temporary issuer failure"); - - assertThrows(RuntimeException.class, - () -> service.rotate(initialToken.rawToken(), response(), ignored -> { - throw failure; - })); - - verify(sessionStore, never()).delete(session.getSessionId()); - RefreshRotationResult pending = rotations.get(initialToken.fingerprint()); - assertTrue(pending != null && !pending.isComplete()); - String pendingRefreshToken = codec.decrypt(pending.getEncryptedRefreshToken()); - - LoginResp recovered = service.rotate(initialToken.rawToken(), response(), - issuer("access-recovered")); - - assertEquals("access-recovered", recovered.getAccessToken()); - assertEquals(pendingRefreshToken, recovered.getRefreshToken()); - } - - @Test - void shouldRevokeWhenSecurityVersionChangesDuringRefresh() { - when(sessionStore.isSecurityVersionCurrent(session)).thenReturn(true, false); - - RefreshTokenException exception = assertThrows(RefreshTokenException.class, - () -> service.rotate(initialToken.rawToken(), response(), issuer("access-1"))); - - assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatus()); - verify(sessionStore).delete(session.getSessionId()); - verify(sessionStore).removeIndexes(session); - } - - @Test - void shouldRejectCookieAndBodyConflict() { - MockHttpServletRequest request = request(); - request.setCookies(new Cookie("refresh_token", initialToken.rawToken())); - - RefreshTokenException exception = assertThrows(RefreshTokenException.class, - () -> service.resolve(initialToken.rawToken(), request)); - assertEquals(HttpStatus.FORBIDDEN, exception.getStatus()); - } - - @Test - void shouldRequireTrustedOriginForCookieMode() { - session.setMode(RefreshTokenModeEnum.COOKIE); - MockHttpServletRequest request = request(); - request.setCookies(new Cookie("refresh_token", initialToken.rawToken())); - request.addHeader("Origin", "https://attacker.example"); - - assertThrows(BusinessException.class, - () -> service.validateRequest(initialToken.rawToken(), request)); - - request.removeHeader("Origin"); - request.addHeader("Origin", "https://admin.example"); - service.validateRequest(initialToken.rawToken(), request); - } - - @Test - void shouldValidateCookieOriginBeforeParsingMalformedToken() { - MockHttpServletRequest request = request(); - request.setCookies(new Cookie("refresh_token", "malformed")); - request.addHeader("Origin", "https://admin.example"); - - service.validateCookieOrigin(request); - assertThrows(BusinessException.class, - () -> service.validateRequest("malformed", request)); - - request.removeHeader("Origin"); - request.addHeader("Origin", "https://attacker.example"); - RefreshTokenException exception = assertThrows(RefreshTokenException.class, - () -> service.validateCookieOrigin(request)); - assertEquals(HttpStatus.FORBIDDEN, exception.getStatus()); - } - - @Test - void shouldRequireTrustedOriginForCachedOlderCookieToken() { - session.setMode(RefreshTokenModeEnum.COOKIE); - session.setCurrentTokenFingerprint(codec.issue(session.getSessionId()).fingerprint()); - RefreshRotationResult cached = new RefreshRotationResult(); - cached.setEncryptedAccessToken(codec.encrypt("access-1")); - rotations.put(initialToken.fingerprint(), cached); - MockHttpServletRequest request = request(); - request.setCookies(new Cookie("refresh_token", initialToken.rawToken())); - request.addHeader("Origin", "https://attacker.example"); - - assertThrows(BusinessException.class, - () -> service.validateRequest(initialToken.rawToken(), request)); - } - - @Test - void shouldRejectWildcardCookieOriginConfiguration() { - properties.setCookieAllowedOrigins(List.of("*")); - - assertFalse(properties.isCookieAllowedOriginsValid()); - } - - @Test - void shouldAllowCookieOriginWildcardForConfiguredSubdomains() { - properties.setCookieAllowedOrigins(List.of("http://*.luoqiz.top")); - assertTrue(properties.isCookieAllowedOriginsValid()); - RefreshTokenRequestGuard requestGuard = new RefreshTokenRequestGuard(properties, codec, - sessionStore); - MockHttpServletRequest request = request(); - request.setCookies(new Cookie("refresh_token", initialToken.rawToken())); - request.addHeader("Origin", "http://admin.luoqiz.top"); - - assertDoesNotThrow(() -> requestGuard.validateCookieOrigin(request)); - - request.removeHeader("Origin"); - request.addHeader("Origin", "http://admin.luoqiz.top.attacker.example"); - assertThrows(RefreshTokenException.class, () -> requestGuard.validateCookieOrigin(request)); - - request.removeHeader("Origin"); - request.addHeader("Origin", "http://nested.admin.luoqiz.top"); - assertThrows(RefreshTokenException.class, () -> requestGuard.validateCookieOrigin(request)); - } - - @Test - void shouldRevokeRefreshSessionEvenWhenAccessTokenIndexAlreadyExpired() { - RefreshSession oldSession = session("old-web", "WEB", 1L); - when(sessionStore.findByUser(1L)).thenReturn(Set.of(oldSession.getSessionId())); - when(sessionStore.get(oldSession.getSessionId())).thenReturn(oldSession); - - RefreshClientPolicy client = client(false, SessionReplacementScope.ALL_CLIENT_TYPES, -1); - String result = service.executeLoginPolicy(1L, client.clientId(), 2L, - version -> new LoginAttempt<>(1L, client, null, null, () -> "issued")); - - assertEquals("issued", result); - verify(sessionStore).delete(oldSession.getSessionId()); - } - - @Test - void shouldOnlyRevokeSameClientTypeForCurrentDevicePolicy() { - RefreshSession webSession = session("old-web", "WEB", 1L); - RefreshSession appSession = session("old-app", "APP", 2L); - when(sessionStore.findByUser(1L)) - .thenReturn(Set.of(webSession.getSessionId(), appSession.getSessionId())); - when(sessionStore.get(webSession.getSessionId())).thenReturn(webSession); - when(sessionStore.get(appSession.getSessionId())).thenReturn(appSession); - - RefreshClientPolicy client = client(false, SessionReplacementScope.CURRENT_CLIENT_TYPE, -1); - service.executeLoginPolicy(1L, client.clientId(), 2L, - version -> new LoginAttempt<>(1L, client, null, null, () -> null)); - - verify(sessionStore).delete(webSession.getSessionId()); - verify(sessionStore, never()).delete(appSession.getSessionId()); - } - - @Test - void shouldApplyMaxLoginCountOnlyToCurrentClientType() { - RefreshSession oldest = session("oldest", "WEB", 1L); - RefreshSession newest = session("newest", "WEB", 2L); - RefreshSession app = session("app", "APP", 3L); - when(sessionStore.findByUser(1L)) - .thenReturn(Set.of(newest.getSessionId(), oldest.getSessionId(), app.getSessionId())); - when(sessionStore.get(oldest.getSessionId())).thenReturn(oldest); - when(sessionStore.get(newest.getSessionId())).thenReturn(newest); - when(sessionStore.get(app.getSessionId())).thenReturn(app); - - RefreshClientPolicy client = client(true, null, 2); - service.executeLoginPolicy(1L, client.clientId(), 2L, - version -> new LoginAttempt<>(1L, client, null, null, () -> null)); - - verify(sessionStore).delete(oldest.getSessionId()); - verify(sessionStore, never()).delete(newest.getSessionId()); - verify(sessionStore, never()).delete(app.getSessionId()); - } - - @Test - void shouldAcquireLoginLocksInFixedOrderAndReleaseInReverseOrder() { - AuthPolicyLock userLock = mock(AuthPolicyLock.class); - AuthPolicyLock tenantLock = mock(AuthPolicyLock.class); - AuthPolicyLock clientLock = mock(AuthPolicyLock.class); - when(sessionStore.lockUserPolicy(1L)).thenReturn(userLock); - when(sessionStore.lockTenantPolicyRead(2L)).thenReturn(tenantLock); - when(sessionStore.lockClientPolicyRead("web")).thenReturn(clientLock); - - RefreshClientPolicy client = client(true, null, -1); - String result = service.executeLoginPolicy(1L, "web", 2L, - version -> new LoginAttempt<>(1L, client, null, null, () -> "issued")); - - assertEquals("issued", result); - org.mockito.InOrder order = inOrder(sessionStore, userLock, tenantLock, clientLock); - order.verify(sessionStore).lockUserPolicy(1L); - order.verify(sessionStore).lockTenantPolicyRead(2L); - order.verify(sessionStore).lockClientPolicyRead("web"); - order.verify(clientLock).close(); - order.verify(tenantLock).close(); - order.verify(userLock).close(); - } - - @Test - void shouldHoldInvalidationLockUntilTransactionCompletion() { - AuthPolicyLock userLock = mock(AuthPolicyLock.class); - RefreshSession oldSession = session("old-web", "WEB", 1L); - when(sessionStore.lockUserPolicy(1L)).thenReturn(userLock); - when(sessionStore.findByUser(1L)).thenReturn(Set.of(oldSession.getSessionId())); - when(sessionStore.get(oldSession.getSessionId())).thenReturn(oldSession); - - TransactionSynchronizationManager.setActualTransactionActive(true); - TransactionSynchronizationManager.initSynchronization(); - try { - service.revokeByUser(1L); - - verify(sessionStore, times(1)).incrementUserSecurityVersion(1L); - verify(userLock, never()).close(); - for (TransactionSynchronization synchronization : TransactionSynchronizationManager - .getSynchronizations()) { - synchronization.afterCompletion(TransactionSynchronization.STATUS_COMMITTED); - } - verify(userLock).close(); - // 不再依赖 afterCommit 二次执行 Redis 失效操作。 - verify(sessionStore, times(1)).incrementUserSecurityVersion(1L); - } finally { - TransactionSynchronizationManager.clearSynchronization(); - TransactionSynchronizationManager.setActualTransactionActive(false); - } - } - - @Test - void shouldFailClosedWhenTransactionSynchronizationIsUnavailable() { - AuthPolicyLock userLock = mock(AuthPolicyLock.class); - when(sessionStore.lockUserPolicy(1L)).thenReturn(userLock); - TransactionSynchronizationManager.setActualTransactionActive(true); - try { - assertThrows(IllegalStateException.class, () -> service.revokeByUser(1L)); - - verify(sessionStore, never()).incrementUserSecurityVersion(1L); - verify(userLock).close(); - } finally { - TransactionSynchronizationManager.setActualTransactionActive(false); - } - } - - @Test - void shouldNotifyWebSocketClusterWhenSessionIsRevoked() { - RefreshSession oldSession = session("old-web", "WEB", 1L); - when(sessionStore.findByUser(1L)).thenReturn(Set.of(oldSession.getSessionId())); - when(sessionStore.get(oldSession.getSessionId())).thenReturn(oldSession); - - service.revokeByUser(1L); - - verify(sessionRevocationNotifier).notifyRevoked(oldSession.getSessionId()); - // 用户级强制下线在 Redis 中记录失效原因,让被踢方下次请求看到准确提示。 - verify(sessionStore).saveLogoutReason(oldSession.getSessionId(), LogoutReasonEnum.KICKOUT); - } - - @Test - void shouldSkipSessionStoreWhenAccessSessionCacheHit() { - String sessionId = initialToken.sessionId(); - when(accessSessionCache.isValid(sessionId)).thenReturn(true); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("access-token-1", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn(sessionId); - assertNull(service.getInvalidReason("access-token-1")); - } - verify(sessionStore, never()).get(anyString()); - } - - @Test - void shouldMarkValidAfterFullValidation() { - String sessionId = initialToken.sessionId(); - when(accessSessionCache.isValid(sessionId)).thenReturn(false); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("access-token-1", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn(sessionId); - stpUtil.when(() -> StpUtil.getLoginIdByToken("access-token-1")).thenReturn(1L); - assertNull(service.getInvalidReason("access-token-1")); - } - verify(sessionStore).get(sessionId); - verify(accessSessionCache).markValid(sessionId); - } - - @Test - void shouldInvalidateAccessSessionCacheWhenSessionRevoked() { - service.revokeBySessionId(initialToken.sessionId()); - verify(accessSessionCache).invalidate(initialToken.sessionId()); - } - - private MockHttpServletRequest request() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setScheme("https"); - request.setServerName("admin.example"); - request.setServerPort(443); - request.setRemoteAddr("127.0.0.1"); - return request; - } - - private RefreshSession session(String sessionId, String clientType, long createdAt) { - RefreshSession value = new RefreshSession(); - value.setSessionId(sessionId); - value.setUserId(1L); - value.setClientId(clientType.toLowerCase()); - value.setClientType(clientType); - value.setCreatedAt(createdAt); - value.setExpiresAt(System.currentTimeMillis() + Duration.ofDays(1).toMillis()); - return value; - } - - private RefreshClientPolicy client(boolean concurrent, SessionReplacementScope replacementScope, - int maxLoginCount) { - return new RefreshClientPolicy("web", "WEB", 2592000L, - RefreshTokenModeEnum.COOKIE, concurrent, replacementScope, maxLoginCount, - LogoutReasonEnum.REPLACED); - } - - private MockHttpServletResponse response() { - return new MockHttpServletResponse(); - } - - private Function issuer(String accessToken) { - return ignored -> LoginResp.builder() - .accessToken(accessToken) - .tokenType("Bearer") - .expiresIn(900L) - .tenantId(1L) - .build(); - } -} diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/AccessSessionCacheTest.java b/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/AccessSessionCacheTest.java deleted file mode 100644 index 200cc7f1d..000000000 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/AccessSessionCacheTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.redisson.api.RTopic; -import org.redisson.api.RedissonClient; -import org.redisson.api.listener.MessageListener; -import top.continew.admin.auth.config.RefreshTokenProperties; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** 热路径会话缓存:命中/失效/广播隔离/载荷防御测试。 */ -class AccessSessionCacheTest { - - private RefreshTokenProperties properties; - private RedissonClient redissonClient; - private RTopic invalidTopic; - private AccessSessionCache cache; - - @BeforeEach - void setUp() { - properties = new RefreshTokenProperties(); - redissonClient = mock(RedissonClient.class); - invalidTopic = mock(RTopic.class); - when(redissonClient.getTopic(anyString())).thenReturn(invalidTopic); - cache = new AccessSessionCache(properties, redissonClient, "test-app"); - cache.init(); - } - - @Test - void shouldReturnFalseBeforeMarkedValid() { - assertFalse(cache.isValid("sid-1")); - } - - @Test - void shouldReturnTrueAfterMarkedValid() { - cache.markValid("sid-1"); - assertTrue(cache.isValid("sid-1")); - } - - @Test - void shouldInvalidateLocallyAndBroadcast() { - cache.markValid("sid-1"); - cache.invalidate("sid-1"); - assertFalse(cache.isValid("sid-1")); - verify(invalidTopic).publish("sid-1"); - } - - @Test - void shouldNotCacheOrPublishWhenDisabled() { - properties.setAccessSessionCacheEnabled(false); - cache.markValid("sid-1"); - assertFalse(cache.isValid("sid-1")); - cache.invalidate("sid-1"); - verify(invalidTopic, never()).publish(anyString()); - } - - @Test - void shouldUseAppNameScopedTopicByDefault() { - verify(redissonClient).getTopic("auth:access-session-invalid:test-app"); - } - - @Test - void shouldUseExplicitTopicWhenConfigured() { - properties.setAccessSessionInvalidTopic("shared-topic"); - AccessSessionCache sharedCache = new AccessSessionCache(properties, redissonClient, - "test-app"); - sharedCache.init(); - verify(redissonClient).getTopic("shared-topic"); - } - - @Test - void shouldFallbackToUnknownAppName() { - AccessSessionCache unnamedCache = new AccessSessionCache(properties, redissonClient, ""); - unnamedCache.init(); - verify(redissonClient).getTopic("auth:access-session-invalid:unknown"); - } - - @Test - @SuppressWarnings("unchecked") - void shouldIgnoreBlankBroadcastMessage() { - cache.markValid("sid-1"); - ArgumentCaptor> captor = ArgumentCaptor - .forClass(MessageListener.class); - verify(invalidTopic).addListener(any(Class.class), captor.capture()); - MessageListener listener = captor.getValue(); - listener.onMessage("channel", ""); - assertTrue(cache.isValid("sid-1")); - listener.onMessage("channel", "sid-1"); - assertFalse(cache.isValid("sid-1")); - } -} diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/RefreshTokenCodecTest.java b/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/RefreshTokenCodecTest.java deleted file mode 100644 index a9c5db424..000000000 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/RefreshTokenCodecTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.admin.auth.support.RefreshTokenCodec.IssuedToken; -import top.continew.admin.auth.support.RefreshTokenCodec.ParsedToken; -import top.continew.admin.auth.exception.RefreshTokenException; -import top.continew.starter.core.exception.BusinessException; - -import java.util.Base64; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** Refresh Token 编解码安全边界测试。 */ -class RefreshTokenCodecTest { - - private RefreshTokenCodec codec; - - @BeforeEach - void setUp() { - RefreshTokenProperties properties = new RefreshTokenProperties(); - properties.setSecret("test-only-refresh-token-secret-with-32-bytes"); - codec = new RefreshTokenCodec(properties); - } - - @Test - void shouldIssueSessionBoundToken() { - String sessionId = codec.newSessionId(); - IssuedToken issued = codec.issue(sessionId); - ParsedToken parsed = codec.parse(issued.rawToken()); - - assertEquals(sessionId, parsed.sessionId()); - assertEquals(66, issued.rawToken().length()); - assertTrue(codec.matches(issued.fingerprint(), parsed.fingerprint())); - assertFalse(issued.rawToken().contains(issued.fingerprint())); - } - - @Test - void shouldGenerateIndependentSecretsForSameSession() { - String sessionId = codec.newSessionId(); - IssuedToken first = codec.issue(sessionId); - IssuedToken second = codec.issue(sessionId); - - assertNotEquals(first.rawToken(), second.rawToken()); - assertNotEquals(first.fingerprint(), second.fingerprint()); - } - - @Test - void shouldRejectMalformedToken() { - RefreshTokenException exception = assertThrows(RefreshTokenException.class, - () -> codec.parse("missing-separator")); - assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatus()); - assertThrows(BusinessException.class, () -> codec.parse("a.b.c")); - assertThrows(BusinessException.class, () -> codec.parse("../unsafe.secret")); - } - - @Test - void shouldEncryptWithRandomIvAndDetectTampering() { - String value = codec.issue(codec.newSessionId()).rawToken(); - String first = codec.encrypt(value); - String second = codec.encrypt(value); - - assertNotEquals(first, second); - assertEquals(value, codec.decrypt(first)); - byte[] tamperedPayload = Base64.getUrlDecoder().decode(first); - tamperedPayload[tamperedPayload.length - 1] ^= 1; - String tampered = Base64.getUrlEncoder().withoutPadding().encodeToString(tamperedPayload); - assertThrows(BusinessException.class, () -> codec.decrypt(tampered)); - } -} diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolverTest.java b/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolverTest.java deleted file mode 100644 index dff1c088b..000000000 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/support/UserArgumentPolicyLockTargetResolverTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.support; - -import org.junit.jupiter.api.Test; -import top.continew.admin.auth.api.AuthPolicyLockTarget; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** 用户策略锁参数解析测试。 */ -class UserArgumentPolicyLockTargetResolverTest { - - private final UserArgumentPolicyLockTargetResolver resolver = - new UserArgumentPolicyLockTargetResolver(); - - @Test - void shouldResolveUserIdFromUpdatePasswordSignature() { - List targets = List.copyOf(resolver.resolve( - new Object[] {"old-password", "new-password", 1L})); - - assertEquals(List.of(AuthPolicyLockTarget.user(1L)), targets); - } - - @Test - void shouldResolveUserIdsFromCollectionArgument() { - List targets = List.copyOf(resolver.resolve( - new Object[] {"ignored", List.of(1L, 2L)})); - - assertEquals(List.of(AuthPolicyLockTarget.user(1L), AuthPolicyLockTarget.user(2L)), - targets); - } - - @Test - void shouldRejectArgumentsWithoutUserTarget() { - assertThrows(IllegalArgumentException.class, - () -> resolver.resolve(new Object[] {"old-password", "new-password"})); - } -} diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/websocket/AuthWebSocketSessionServiceTest.java b/continew-auth-refresh/src/test/java/top/continew/admin/auth/websocket/AuthWebSocketSessionServiceTest.java deleted file mode 100644 index aedb8d46c..000000000 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/websocket/AuthWebSocketSessionServiceTest.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package top.continew.admin.auth.websocket; - -import cn.dev33.satoken.stp.StpUtil; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.redisson.api.RTopic; -import org.redisson.api.RedissonClient; -import org.redisson.api.listener.MessageListener; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketSession; -import top.continew.admin.auth.api.AuthSessionConstants; -import top.continew.admin.auth.api.AccessSessionValidator; -import top.continew.admin.auth.config.RefreshTokenProperties; -import top.continew.starter.messaging.websocket.dao.WebSocketSessionDao; - -import java.util.List; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** Refresh Session 撤销与 WebSocket 连接联动测试。 */ -class AuthWebSocketSessionServiceTest { - - @Test - @SuppressWarnings("unchecked") - void shouldCloseLocalConnectionAndPublishClusterRevocation() throws Exception { - RedissonClient redissonClient = mock(RedissonClient.class); - RTopic topic = mock(RTopic.class); - ObjectProvider sessionDaoProvider = mock(ObjectProvider.class); - ObjectProvider authSessionApiProvider = mock(ObjectProvider.class); - WebSocketSessionDao sessionDao = mock(WebSocketSessionDao.class); - WebSocketSession webSocketSession = mock(WebSocketSession.class); - when(redissonClient.getTopic(anyString())).thenReturn(topic); - when(topic.addListener(eq(String.class), any(MessageListener.class))).thenReturn(1); - when(sessionDaoProvider.getIfAvailable()).thenReturn(sessionDao); - when(sessionDao.listAllSessionIds()).thenReturn(Set.of("access-token")); - when(sessionDao.get("access-token")).thenReturn(webSocketSession); - when(webSocketSession.isOpen()).thenReturn(true); - - AuthWebSocketSessionService service = this.service(redissonClient, sessionDaoProvider, - authSessionApiProvider); - service.subscribe(); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("access-token", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-1"); - - service.notifyRevoked("session-1"); - } finally { - service.unsubscribe(); - } - - verify(webSocketSession).close(CloseStatus.POLICY_VIOLATION); - verify(sessionDao).delete("access-token"); - verify(topic).publish("session-1"); - verify(topic).removeListener(1); - } - - @Test - @SuppressWarnings("unchecked") - void shouldStillCloseLocalConnectionWhenClusterPublishFails() throws Exception { - RedissonClient redissonClient = mock(RedissonClient.class); - RTopic topic = mock(RTopic.class); - ObjectProvider sessionDaoProvider = mock(ObjectProvider.class); - ObjectProvider authSessionApiProvider = mock(ObjectProvider.class); - WebSocketSessionDao sessionDao = mock(WebSocketSessionDao.class); - WebSocketSession webSocketSession = mock(WebSocketSession.class); - when(redissonClient.getTopic(anyString())).thenReturn(topic); - when(topic.addListener(eq(String.class), any(MessageListener.class))).thenReturn(1); - when(topic.publish("session-1")).thenThrow(new IllegalStateException("Redis unavailable")); - when(sessionDaoProvider.getIfAvailable()).thenReturn(sessionDao); - when(sessionDao.listAllSessionIds()).thenReturn(Set.of("access-token")); - when(sessionDao.get("access-token")).thenReturn(webSocketSession); - when(webSocketSession.isOpen()).thenReturn(true); - - AuthWebSocketSessionService service = this.service(redissonClient, sessionDaoProvider, - authSessionApiProvider); - service.subscribe(); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("access-token", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-1"); - - service.notifyRevoked("session-1"); - } finally { - service.unsubscribe(); - } - - verify(webSocketSession).close(CloseStatus.POLICY_VIOLATION); - verify(sessionDao).delete("access-token"); - } - - @Test - @SuppressWarnings("unchecked") - void shouldCloseOnlyInvalidConnectionsDuringPeriodicValidation() throws Exception { - RedissonClient redissonClient = mock(RedissonClient.class); - ObjectProvider sessionDaoProvider = mock(ObjectProvider.class); - ObjectProvider authSessionApiProvider = mock(ObjectProvider.class); - WebSocketSessionDao sessionDao = mock(WebSocketSessionDao.class); - AccessSessionValidator authSessionApi = mock(AccessSessionValidator.class); - WebSocketSession invalidSession = mock(WebSocketSession.class); - when(sessionDaoProvider.getIfAvailable()).thenReturn(sessionDao); - when(authSessionApiProvider.getIfAvailable()).thenReturn(authSessionApi); - when(sessionDao.listAllSessionIds()).thenReturn(Set.of("invalid-token", "valid-token")); - when(authSessionApi.isInvalid("invalid-token")).thenReturn(true); - when(authSessionApi.isInvalid("valid-token")).thenReturn(false); - when(sessionDao.get("invalid-token")).thenReturn(invalidSession); - when(invalidSession.isOpen()).thenReturn(true); - - AuthWebSocketSessionService service = this.service(redissonClient, sessionDaoProvider, - authSessionApiProvider); - service.validateLocalSessions(); - - verify(invalidSession).close(CloseStatus.POLICY_VIOLATION); - verify(sessionDao).delete("invalid-token"); - verify(sessionDao, never()).delete("valid-token"); - } - - @Test - @SuppressWarnings("unchecked") - void shouldCloseAllConnectionsOfSameTokenViaMultiDao() throws Exception { - RedissonClient redissonClient = mock(RedissonClient.class); - ObjectProvider sessionDaoProvider = mock(ObjectProvider.class); - ObjectProvider authSessionApiProvider = mock(ObjectProvider.class); - MultiWebSocketSessionDao sessionDao = mock(MultiWebSocketSessionDao.class); - WebSocketSession firstTab = mock(WebSocketSession.class); - WebSocketSession secondTab = mock(WebSocketSession.class); - when(sessionDaoProvider.getIfAvailable()).thenReturn(sessionDao); - when(sessionDao.listAllSessionIds()).thenReturn(Set.of("access-token")); - when(sessionDao.listByKey("access-token")).thenReturn(List.of(firstTab, secondTab)); - when(firstTab.isOpen()).thenReturn(true); - when(secondTab.isOpen()).thenReturn(true); - - AuthWebSocketSessionService service = this.service(redissonClient, sessionDaoProvider, - authSessionApiProvider); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("access-token", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-1"); - - service.notifyRevoked("session-1"); - } - - verify(firstTab).close(CloseStatus.POLICY_VIOLATION); - verify(secondTab).close(CloseStatus.POLICY_VIOLATION); - verify(sessionDao).removeAll("access-token"); - verify(sessionDao, never()).delete(anyString()); - } - - @Test - @SuppressWarnings("unchecked") - void shouldReuseLocalSessionCacheAcrossBatchRevocations() throws Exception { - RedissonClient redissonClient = mock(RedissonClient.class); - ObjectProvider sessionDaoProvider = mock(ObjectProvider.class); - ObjectProvider authSessionApiProvider = mock(ObjectProvider.class); - WebSocketSessionDao sessionDao = mock(WebSocketSessionDao.class); - WebSocketSession sessionA = mock(WebSocketSession.class); - WebSocketSession sessionB = mock(WebSocketSession.class); - WebSocketSession sessionC = mock(WebSocketSession.class); - when(sessionDaoProvider.getIfAvailable()).thenReturn(sessionDao); - // 首次撤销后 token-a 的连接被关闭并从 DAO 移除,后续扫描只剩 b、c。 - when(sessionDao.listAllSessionIds()) - .thenReturn(Set.of("token-a", "token-b", "token-c"), - Set.of("token-b", "token-c")); - when(sessionDao.get("token-a")).thenReturn(sessionA); - when(sessionDao.get("token-b")).thenReturn(sessionB); - when(sessionDao.get("token-c")).thenReturn(sessionC); - when(sessionA.isOpen()).thenReturn(true); - when(sessionB.isOpen()).thenReturn(true); - when(sessionC.isOpen()).thenReturn(true); - - AuthWebSocketSessionService service = this.service(redissonClient, sessionDaoProvider, - authSessionApiProvider); - try (MockedStatic stpUtil = mockStatic(StpUtil.class)) { - stpUtil.when(() -> StpUtil.getExtra("token-a", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-a"); - stpUtil.when(() -> StpUtil.getExtra("token-b", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-b"); - stpUtil.when(() -> StpUtil.getExtra("token-c", - AuthSessionConstants.SESSION_ID_CLAIM)).thenReturn("session-c"); - - // 首次撤销:三个 Token 的会话声明全部冷启动回源并写入本地索引。 - service.notifyRevoked("session-a"); - verify(sessionA).close(CloseStatus.POLICY_VIOLATION); - verify(sessionB, never()).close(any()); - stpUtil.verify(() -> StpUtil.getExtra(anyString(), - anyString()), times(3)); - - // 第二次撤销:token-b/token-c 命中本地索引,不再回源 Redis。 - service.notifyRevoked("session-b"); - verify(sessionB).close(CloseStatus.POLICY_VIOLATION); - stpUtil.verify(() -> StpUtil.getExtra(anyString(), - anyString()), times(3)); - } - } - - private AuthWebSocketSessionService service(RedissonClient redissonClient, - ObjectProvider sessionDaoProvider, - ObjectProvider accessSessionValidatorProvider) { - return new AuthWebSocketSessionService(redissonClient, sessionDaoProvider, - accessSessionValidatorProvider, new RefreshTokenProperties()); - } - - @Test - void daoShouldKeepOtherTabsWhenOneConnectionCloses() throws Exception { - ConcurrentWebSocketSessionDao dao = new ConcurrentWebSocketSessionDao(); - WebSocketSession closed = mock(WebSocketSession.class); - WebSocketSession alive = mock(WebSocketSession.class); - when(closed.getId()).thenReturn("conn-1"); - when(alive.getId()).thenReturn("conn-2"); - when(closed.isOpen()).thenReturn(false); - when(alive.isOpen()).thenReturn(true); - - dao.add("access-token", closed); - dao.add("access-token", alive); - List registered = List.copyOf(dao.listByKey("access-token")); - assertEquals(2, registered.size()); - assertTrue(registered.containsAll(List.of(closed, alive))); - assertEquals(alive, dao.get("access-token")); - assertEquals(Set.of("access-token"), dao.listAllSessionIds()); - - // Starter 关闭回调只携带 Key:只移除已关闭的连接,保留存活的标签页。 - dao.delete("access-token"); - assertEquals(List.of(alive), dao.listByKey("access-token")); - assertEquals(alive, dao.get("access-token")); - - dao.removeAll("access-token"); - assertEquals(List.of(), dao.listByKey("access-token")); - assertNull(dao.get("access-token")); - } -} diff --git a/continew-common/pom.xml b/continew-common/pom.xml index 0cdfc0582..5c1e7c06c 100644 --- a/continew-common/pom.xml +++ b/continew-common/pom.xml @@ -114,6 +114,18 @@ continew-starter-auth-justauth + + + top.continew.starter + continew-starter-auth-refresh-token + + + org.springframework.boot + spring-boot-starter-web + + + + top.continew.starter diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/web/RefreshTokenExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java similarity index 93% rename from continew-auth-refresh/src/main/java/top/continew/admin/auth/web/RefreshTokenExceptionHandler.java rename to continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java index 88666250d..18d4c8f21 100644 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/web/RefreshTokenExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package top.continew.admin.auth.web; +package top.continew.admin.common.config.exception; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -22,7 +22,7 @@ import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import top.continew.admin.auth.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.starter.web.model.R; /** Refresh Token HTTP 协议异常处理器。 */ diff --git a/continew-plugin/continew-plugin-tenant/pom.xml b/continew-plugin/continew-plugin-tenant/pom.xml index fad4afd9f..bb0db90c6 100644 --- a/continew-plugin/continew-plugin-tenant/pom.xml +++ b/continew-plugin/continew-plugin-tenant/pom.xml @@ -16,11 +16,6 @@ 租户插件 - - - ${project.groupId} - continew-auth-refresh - org.springframework.boot spring-boot-starter-test diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java index eb74522db..e115bc990 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java @@ -18,8 +18,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import top.continew.admin.common.api.tenant.TenantApi; import java.util.Collection; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java index bd14f16dd..b67d5103c 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java @@ -21,7 +21,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.admin.tenant.auth.PackageTenantPolicyLockTargetResolver; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.enums.DisEnableStatusEnum; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java index 4a002f2e3..f94fd8169 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java @@ -26,8 +26,8 @@ import me.ahoo.cosid.provider.IdGeneratorProvider; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; -import top.continew.admin.auth.support.TenantArgumentPolicyLockTargetResolver; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.support.TenantArgumentPolicyLockTargetResolver; import top.continew.admin.common.api.system.RoleApi; import top.continew.admin.common.api.system.RoleMenuApi; import top.continew.admin.common.api.tenant.TenantDataApi; diff --git a/continew-server/pom.xml b/continew-server/pom.xml index 9fa158b09..7c76f6eb4 100644 --- a/continew-server/pom.xml +++ b/continew-server/pom.xml @@ -22,11 +22,6 @@ continew-system - - - ${project.groupId} - continew-auth-refresh - diff --git a/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java b/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java index 2bbaafc9c..cd81bb2d0 100644 --- a/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java +++ b/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java @@ -69,7 +69,7 @@ public class ContiNewAdminApplication implements ApplicationRunner { public static void main(String[] args) { SpringApplication application = new SpringApplication(ContiNewAdminApplication.class); application.setDefaultProperties( - MapUtil.of("continew-starter.version", ContiNewStarterVersion.getVersion())); + MapUtil.of("continew-starter.version", ContiNewStarterVersion.VERSION)); application.run(args); } @@ -88,7 +88,7 @@ public void run(ApplicationArguments args) { String baseUrl = URLUtil.normalize("%s:%s%s".formatted(hostAddress, port, contextPath)); log.info("--------------------------------------------------------"); log.info("{} server started successfully.", applicationProperties.getName()); - log.info("ContiNew Starter: v{} (Spring Boot: v{})", ContiNewStarterVersion.getVersion(), + log.info("ContiNew Starter: v{} (Spring Boot: v{})", ContiNewStarterVersion.VERSION, SpringBootVersion .getVersion()); log.info("当前版本: v{} (Profile: {})", applicationProperties.getVersion(), SpringUtil diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java b/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java index 130545826..960322266 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java @@ -38,7 +38,8 @@ public class AccessTokenSecretValidator { public void validate() { String accessSecret = this.bindSecret("sa-token.jwt-secret-key", "Access Token JWT 密钥"); - String refreshSecret = this.bindSecret("auth.refresh-token.secret", "Refresh Token 密钥"); + String refreshSecret = this.bindSecret("continew-starter.refresh-token.secret", + "Refresh Token 密钥"); if (MessageDigest.isEqual(accessSecret.getBytes(StandardCharsets.UTF_8), refreshSecret.getBytes(StandardCharsets.UTF_8))) { throw new IllegalStateException( diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java b/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java index 15352cbe2..37aeb1869 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java @@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; -import top.continew.admin.auth.api.AccessSessionValidator; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.auth.constant.AuthConstants; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; @@ -32,7 +32,7 @@ import top.continew.admin.open.util.OpenApiUtils; import top.continew.starter.core.util.ServletUtils; import top.continew.starter.extension.tenant.context.TenantContextHolder; -import top.continew.starter.json.jackson.util.JSONUtils; +import top.continew.starter.json.jackson.util.JsonUtils; import top.continew.starter.web.model.R; import java.util.Objects; @@ -80,7 +80,7 @@ public boolean preHandle(HttpServletRequest request, UserContextHolder.clearContext(); R r = R.fail(String.valueOf(HttpStatus.UNAUTHORIZED.value()), invalidReason); response.setStatus(HttpStatus.UNAUTHORIZED.value()); - ServletUtils.writeJSON(response, JSONUtils.toJsonStr(r)); + ServletUtils.writeJson(response, JsonUtils.toJsonStr(r)); return false; } } @@ -102,7 +102,7 @@ public boolean preHandle(HttpServletRequest request, UserContextHolder.clearContext(); R r = R.fail(String.valueOf(HttpStatus.FORBIDDEN.value()), "您当前没有访问该租户的权限"); response.setStatus(HttpStatus.FORBIDDEN.value()); - ServletUtils.writeJSON(response, JSONUtils.toJsonStr(r)); + ServletUtils.writeJson(response, JsonUtils.toJsonStr(r)); return false; } } diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java b/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java index 735803d68..a4e41d472 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java @@ -37,7 +37,7 @@ import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationUtils; import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache; -import top.continew.admin.auth.api.AccessSessionValidator; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.open.sign.OpenApiSignTemplate; diff --git a/continew-server/src/main/resources/config/application-dev.yml b/continew-server/src/main/resources/config/application-dev.yml index e304d2662..819420d1e 100644 --- a/continew-server/src/main/resources/config/application-dev.yml +++ b/continew-server/src/main/resources/config/application-dev.yml @@ -3,16 +3,16 @@ application: # URL(跨域配置默认放行此 URL,第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL) url: http://localhost:5173 ---- ### 认证配置 -auth: - ## Refresh Token 配置。浏览器使用 HttpOnly Cookie,App / 小程序使用 BODY 模式。 - refresh-token: - # 团队共享的开发环境固定密钥;仅用于 dev,禁止与 Access Token 的 JWT 密钥复用。 - secret: 80B38761234D4D108BAF2022065FE6E6 - # 可配置精确 Origin 或 http[s]://*.example.com 子域通配符。 - cookie-allowed-origins: - - http://localhost:5173 - - http://localhost:5777 +--- ### Refresh Token 配置 +continew-starter.refresh-token: + # 团队共享的开发环境固定密钥;仅用于 dev,禁止与 Access Token 的 JWT 密钥复用。 + secret: 80B38761234D4D108BAF2022065FE6E6 + # 可配置精确 Origin 或 http[s]://*.example.com 子域通配符。 + cookie-allowed-origins: + - http://localhost:5173 + - http://localhost:5777 + # 本地开发使用 HTTP,显式确认知情选择不安全 Cookie 传输,避免 Starter 启动告警。 + cookie-insecure-allowed: true --- ### Sa-Token 配置 sa-token: diff --git a/continew-server/src/main/resources/config/application-prod.yml b/continew-server/src/main/resources/config/application-prod.yml index 849298894..a2d939bf7 100644 --- a/continew-server/src/main/resources/config/application-prod.yml +++ b/continew-server/src/main/resources/config/application-prod.yml @@ -5,21 +5,19 @@ application: # 是否为生产环境 production: true ---- ### 认证配置 -auth: - ## Refresh Token 配置。浏览器使用 HttpOnly Cookie,App / 小程序使用 BODY 模式。 - refresh-token: - # 生产环境必须显式提供独立的高熵密钥,不允许回退复用 JWT 密钥。 - secret: ${REFRESH_TOKEN_SECRET} - # 生产环境只允许通过 HTTPS 发送长期凭证。 - cookie-secure: true - # __Host- 前缀禁止 Domain 属性并强制 Path=/,可防止子域写入同名 Cookie。 - cookie-name: __Host-refresh_token - # 覆盖网关前缀及前后端分离部署,Refresh Token Cookie 在站点根路径生效。 - cookie-path: / - # Cookie 请求来源可填精确 Origin 或 http[s]://*.example.com 子域通配符,不继承通用 CORS 配置。 - cookie-allowed-origins: - - ${application.url} +--- ### Refresh Token 配置 +continew-starter.refresh-token: + # 生产环境必须显式提供独立的高熵密钥,不允许回退复用 JWT 密钥。 + secret: ${REFRESH_TOKEN_SECRET} + # 生产环境只允许通过 HTTPS 发送长期凭证。 + cookie-secure: true + # __Host- 前缀禁止 Domain 属性并强制 Path=/,可防止子域写入同名 Cookie。 + cookie-name: __Host-refresh_token + # 覆盖网关前缀及前后端分离部署,Refresh Token Cookie 在站点根路径生效。 + cookie-path: / + # Cookie 请求来源可填精确 Origin 或 http[s]://*.example.com 子域通配符,不继承通用 CORS 配置。 + cookie-allowed-origins: + - ${application.url} --- ### 服务器配置 server: # HTTP 端口(默认 8080) diff --git a/continew-server/src/main/resources/config/application.yml b/continew-server/src/main/resources/config/application.yml index a2e030a80..cd9532af1 100644 --- a/continew-server/src/main/resources/config/application.yml +++ b/continew-server/src/main/resources/config/application.yml @@ -354,37 +354,39 @@ cosid: char-size: 11 pad-start: true +--- ### Refresh Token 配置 +## 浏览器使用 HttpOnly Cookie,App / 小程序使用 BODY 模式。 +continew-starter.refresh-token: + # 必须设置独立的高熵随机值,禁止与 Access Token 的 JWT 密钥复用。 + secret: ${REFRESH_TOKEN_SECRET} + cookie-name: refresh_token + # 必须覆盖前端代理路径(如 /api、/dev-api),否则浏览器刷新请求不会携带 Cookie。 + cookie-path: / + # 本地开发通常使用 HTTP;生产配置在 application-prod.yml 中覆盖为 true。 + cookie-secure: false + cookie-same-site: Lax + # Cookie 来源使用独立白名单;可填精确 Origin 或 http[s]://*.example.com 子域通配符。 + # 空列表仅允许同源,不继承通用 CORS 通配符。 + cookie-allowed-origins: [] + rotation-grace-period: 5 + ip-rate-limit: 60 + ip-rate-limit-period: 60 + session-rate-limit: 10 + session-rate-limit-period: 60 + # 仅在网关地址和跳数均显式配置时解析 X-Forwarded-For;默认使用连接对端地址防伪造。 + # 警告:经 Nginx / 网关部署必须显式配置 trusted-proxy-*,否则所有用户共享网关这一个 + # IP 的限流桶(ip-rate-limit 60 次/分钟变全站共享),且 /auth/refresh 为匿名接口, + # 公共桶易被填满导致全站 429。 + trusted-proxy-addresses: [] + trusted-proxy-hops: 0 + # 热路径会话校验本地缓存:命中后每个请求 0 次 Redis;撤销经广播子秒级失效,2s TTL 兜底。 + access-session-cache-enabled: true + # 会话失效广播 Topic(默认按应用名隔离:auth:access-session-invalid:{spring.application.name}, + # 同一服务多副本共享、不同服务互不串扰);多服务显式共享会话域时才覆盖为公共 Topic。 + # access-session-invalid-topic: auth:access-session-invalid:${spring.application.name} + --- ### 认证配置 auth: - ## Refresh Token 配置。浏览器使用 HttpOnly Cookie,App / 小程序使用 BODY 模式。 - refresh-token: - # 必须设置独立的高熵随机值,禁止与 Access Token 的 JWT 密钥复用。 - secret: ${REFRESH_TOKEN_SECRET} - cookie-name: refresh_token - # 必须覆盖前端代理路径(如 /api、/dev-api),否则浏览器刷新请求不会携带 Cookie。 - cookie-path: / - # 本地开发通常使用 HTTP;生产配置在 application-prod.yml 中覆盖为 true。 - cookie-secure: false - cookie-same-site: Lax - # Cookie 来源使用独立白名单;可填精确 Origin 或 http[s]://*.example.com 子域通配符。 - # 空列表仅允许同源,不继承通用 CORS 通配符。 - cookie-allowed-origins: [] - rotation-grace-period: 5 - ip-rate-limit: 60 - ip-rate-limit-period: 60 - session-rate-limit: 10 - session-rate-limit-period: 60 - # 仅在网关地址和跳数均显式配置时解析 X-Forwarded-For;默认使用连接对端地址防伪造。 - # 警告:经 Nginx / 网关部署必须显式配置 trusted-proxy-*,否则所有用户共享网关这一个 - # IP 的限流桶(ip-rate-limit 60 次/分钟变全站共享),且 /auth/refresh 为匿名接口, - # 公共桶易被填满导致全站 429。 - trusted-proxy-addresses: [] - trusted-proxy-hops: 0 - # 热路径会话校验本地缓存:命中后每个请求 0 次 Redis;撤销经广播子秒级失效,2s TTL 兜底。 - access-session-cache-enabled: true - # 会话失效广播 Topic(默认按应用名隔离:auth:access-session-invalid:{spring.application.name}, - # 同一服务多副本共享、不同服务互不串扰);多服务显式共享会话域时才覆盖为公共 Topic。 - # access-session-invalid-topic: auth:access-session-invalid:${spring.application.name} ## 密码配置 password: excludes: diff --git a/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java b/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java index b3596dc95..31e4f36f2 100644 --- a/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java +++ b/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java @@ -18,10 +18,13 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import top.continew.admin.auth.model.SessionView; import top.continew.admin.auth.model.query.OnlineUserQuery; import top.continew.admin.auth.model.resp.OnlineUserResp; import top.continew.admin.auth.service.impl.OnlineUserServiceImpl; +import top.continew.starter.auth.refresh.token.model.RefreshSession; +import top.continew.starter.auth.refresh.token.model.SessionView; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; +import top.continew.starter.auth.refresh.token.service.SessionQueryService; import top.continew.starter.extension.tenant.context.TenantContextHolder; import java.time.Instant; @@ -42,15 +45,16 @@ void shouldListRefreshSessionAfterAccessTokenExpires() { SessionInvalidationService sessionInvalidationService = mock(SessionInvalidationService.class); SessionQueryService sessionQueryService = mock(SessionQueryService.class); - SessionView session = new SessionView(); - session.setSessionId("session-1"); - session.setUserId(1L); - session.setUsername("tester"); - session.setNickname("Tester"); - session.setClientId("web"); - session.setClientType("WEB"); - session.setCreatedAt(1_767_262_400_000L); - session.setLastRefreshAt(1_767_266_000_000L); + RefreshSession refreshSession = new RefreshSession(); + refreshSession.setSessionId("session-1"); + refreshSession.setUserId(1L); + refreshSession.setUsername("tester"); + refreshSession.setNickname("Tester"); + refreshSession.setClientId("web"); + refreshSession.setClientType("WEB"); + refreshSession.setCreatedAt(1_767_262_400_000L); + refreshSession.setLastRefreshAt(1_767_266_000_000L); + SessionView session = SessionView.from(refreshSession); when(sessionQueryService.listSessions(null)).thenReturn(List.of(session)); OnlineUserServiceImpl service = new OnlineUserServiceImpl(sessionInvalidationService, diff --git a/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java b/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java index f025af121..70f2700c5 100644 --- a/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java +++ b/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java @@ -33,7 +33,7 @@ import org.mockito.MockedStatic; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import top.continew.admin.auth.api.AccessSessionValidator; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.starter.extension.tenant.context.TenantContextHolder; diff --git a/continew-system/pom.xml b/continew-system/pom.xml index 05475d48f..591b4360b 100644 --- a/continew-system/pom.xml +++ b/continew-system/pom.xml @@ -22,16 +22,28 @@ continew-common - - - ${project.groupId} - continew-auth-refresh - - org.dromara.sms4j sms4j-spring-boot-starter + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java index 9e947dd38..971bc386f 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java @@ -19,8 +19,8 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import top.continew.admin.system.mapper.ClientMapper; import top.continew.admin.system.model.entity.ClientDO; diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java index 7c2a33cbe..efdf2d5e5 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java @@ -19,8 +19,8 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.admin.auth.api.AuthPolicyLockTarget; -import top.continew.admin.auth.api.AuthPolicyLockTargetResolver; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import top.continew.admin.system.mapper.user.UserMapper; import top.continew.admin.system.model.entity.user.UserDO; diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java index 50967482e..72b6cb531 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java @@ -21,13 +21,14 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.admin.auth.model.RefreshSession; +import top.continew.starter.auth.refresh.token.model.IssuedAccessToken; +import top.continew.starter.auth.refresh.token.model.RefreshSession; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.service.AuthTokenService; -import top.continew.admin.auth.service.RefreshAccessTokenIssuer; +import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; import top.continew.admin.common.api.tenant.TenantApi; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.auth.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; import top.continew.admin.system.service.ClientService; @@ -48,7 +49,7 @@ public class RefreshAccessTokenIssuerImpl implements RefreshAccessTokenIssuer { private final UserService userService; @Override - public LoginResp issue(RefreshSession session, HttpServletRequest request, + public IssuedAccessToken issue(RefreshSession session, HttpServletRequest request, HttpServletResponse response) { ClientResp client = clientService.getByClientId(session.getClientId()); if (client == null || DisEnableStatusEnum.DISABLE.equals(client.getStatus())) { @@ -65,7 +66,9 @@ public LoginResp issue(RefreshSession session, HttpServletRequest request, if (userReference.get() == null || StrUtil.isBlank(userReference.get().getUsername())) { throw RefreshTokenException.unauthorized("登录状态已失效,请重新登录"); } - return authTokenService.issueAccessToken(userReference.get(), client, session.getTenantId(), - session, request, response); + LoginResp loginResp = authTokenService.issueAccessToken(userReference.get(), client, + session.getTenantId(), session, request, response); + return new IssuedAccessToken(loginResp.getAccessToken(), loginResp.getExpiresIn(), + loginResp.getTenantId()); } } diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java index 974806df4..213b540d2 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java @@ -16,9 +16,9 @@ package top.continew.admin.auth.adapter; -import top.continew.admin.auth.enums.LogoutReasonEnum; -import top.continew.admin.auth.enums.SessionReplacementScope; -import top.continew.admin.auth.model.RefreshClientPolicy; +import top.continew.starter.auth.refresh.token.enums.LogoutReasonEnum; +import top.continew.starter.auth.refresh.token.enums.SessionReplacementScope; +import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; import top.continew.admin.system.enums.LogoutModeEnum; import top.continew.admin.system.enums.ReplacedRangeEnum; import top.continew.admin.system.model.resp.ClientResp; diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshSessionPrincipalAdapter.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshSessionPrincipalAdapter.java new file mode 100644 index 000000000..9a0a739d6 --- /dev/null +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshSessionPrincipalAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.auth.adapter; + +import top.continew.admin.common.context.UserContext; +import top.continew.admin.common.context.UserExtraContext; +import top.continew.starter.auth.refresh.token.model.RefreshSessionPrincipal; + +/** + * 登录主体快照适配器:将 Admin 用户上下文映射为认证会话模块的主体快照。 + * + *

认证会话模块不依赖用户实体,登录签发时由本适配器提供创建会话所需的主体信息, + * 随 Refresh Session 一并保存,用于会话管理展示与失效原因判定。

+ * + * @author Charles7c + * @since 4.2.0 + */ +public record RefreshSessionPrincipalAdapter(UserContext userContext, + UserExtraContext extraContext) implements RefreshSessionPrincipal { + + @Override + public Long getUserId() { + return this.userContext.getId(); + } + + @Override + public String getUsername() { + return this.userContext.getUsername(); + } + + @Override + public String getNickname() { + return this.userContext.getNickname(); + } + + @Override + public Long getTenantId() { + return this.extraContext.getTenantId(); + } + + @Override + public String getIp() { + return this.extraContext.getIp(); + } + + @Override + public String getAddress() { + return this.extraContext.getAddress(); + } + + @Override + public String getBrowser() { + return this.extraContext.getBrowser(); + } + + @Override + public String getOs() { + return this.extraContext.getOs(); + } +} diff --git a/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java b/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java index fc8b60c64..ad331fdbe 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java +++ b/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java @@ -31,13 +31,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import top.continew.admin.auth.model.query.OnlineUserQuery; -import top.continew.admin.auth.model.SessionView; +import top.continew.starter.auth.refresh.token.model.SessionView; import top.continew.admin.auth.model.resp.OnlineUserResp; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.admin.auth.service.SessionInvalidationService; -import top.continew.admin.auth.service.SessionQueryService; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; +import top.continew.starter.auth.refresh.token.service.SessionQueryService; import top.continew.admin.common.context.UserContextHolder; -import top.continew.admin.auth.api.AuthSessionConstants; +import top.continew.starter.auth.refresh.token.api.AuthSessionConstants; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/controller/SessionController.java b/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java similarity index 80% rename from continew-auth-refresh/src/main/java/top/continew/admin/auth/controller/SessionController.java rename to continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java index e5ed8c53f..4659c0c49 100644 --- a/continew-auth-refresh/src/main/java/top/continew/admin/auth/controller/SessionController.java +++ b/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java @@ -34,9 +34,10 @@ import org.springframework.web.bind.annotation.RestController; import top.continew.admin.auth.model.req.RefreshTokenReq; import top.continew.admin.auth.model.resp.LoginResp; -import top.continew.admin.auth.service.RefreshAccessTokenIssuer; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.model.RefreshIssueResult; +import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService; import top.continew.starter.extension.tenant.annotation.TenantIgnore; import top.continew.starter.log.annotation.Log; import top.continew.starter.log.enums.Include; @@ -63,12 +64,14 @@ public class SessionController { @PostMapping("/refresh") public LoginResp refresh(@RequestBody(required = false) @Valid RefreshTokenReq req, HttpServletRequest request, HttpServletResponse response) { - String rawRefreshToken = refreshTokenService.resolve(req == null ? null : req - .getRefreshToken(), request); - refreshTokenService.checkRequestRateLimit(request); - refreshTokenService.validateRequest(rawRefreshToken, request); - return refreshTokenService.rotate(rawRefreshToken, response, - session -> refreshAccessTokenIssuer.issue(session, request, response)); + // 四步安全钩子(解析 → 限流 → 来源校验 → 原子轮换)由 Starter 的 refresh 模板固化编排 + RefreshIssueResult result = refreshTokenService.refresh(req == null ? null : req + .getRefreshToken(), request, response, session -> refreshAccessTokenIssuer.issue(session, + request, response)); + return LoginResp.builder().accessToken(result.getAccessToken()) + .tokenType(result.getTokenType()).expiresIn(result.getExpiresIn()) + .refreshExpiresIn(result.getRefreshExpiresIn()).refreshToken(result.getRefreshToken()) + .tenantId(result.getTenantId()).build(); } /** 注销当前认证会话。 */ diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/req/RefreshTokenReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/RefreshTokenReq.java similarity index 100% rename from continew-auth-refresh/src/main/java/top/continew/admin/auth/model/req/RefreshTokenReq.java rename to continew-system/src/main/java/top/continew/admin/auth/model/req/RefreshTokenReq.java diff --git a/continew-auth-refresh/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java b/continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java similarity index 100% rename from continew-auth-refresh/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java rename to continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java b/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java index f1b72f3eb..d53d6c5bc 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java @@ -18,7 +18,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import top.continew.admin.auth.model.RefreshSession; +import top.continew.starter.auth.refresh.token.model.RefreshSession; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java index c5e9e402c..995628bb8 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java @@ -25,23 +25,24 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; -import top.continew.admin.auth.model.AuthSecurityVersion; -import top.continew.admin.auth.model.RefreshSession; -import top.continew.admin.auth.model.RefreshClientPolicy; +import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; +import top.continew.starter.auth.refresh.token.model.AuthSecurityVersion; +import top.continew.starter.auth.refresh.token.model.RefreshSession; +import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; import top.continew.admin.auth.adapter.RefreshClientPolicyMapper; +import top.continew.admin.auth.adapter.RefreshSessionPrincipalAdapter; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.service.AuthTokenService; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.service.RefreshTokenService.LoginAttempt; -import top.continew.admin.auth.api.AuthSessionConstants; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService.LoginAttempt; +import top.continew.starter.auth.refresh.token.api.AuthSessionConstants; import top.continew.admin.common.api.tenant.TenantApi; import top.continew.admin.common.context.RoleContext; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.context.UserExtraContext; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.auth.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.admin.system.model.entity.DeptDO; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; @@ -159,7 +160,7 @@ private LoginResp issueInTenant(UserDO user, ClientResp client, Long tenantId, RefreshClientPolicy refreshClientPolicy = RefreshClientPolicyMapper.from(client); long accessTokenTimeout = this.getEffectiveAccessTokenTimeout(client.getTimeout(), - issueRefreshToken ? refreshTokenService.getRefreshTimeout(refreshClientPolicy) + issueRefreshToken ? refreshClientPolicy.refreshTokenTimeout() : this.remainingSeconds(refreshSession.getExpiresAt())); // Sa-Token 只管理短期 Access Token 的生命周期。并发登录、顶人范围和最大登录 @@ -203,13 +204,11 @@ private LoginResp issueInTenant(UserDO user, ClientResp client, Long tenantId, // 新登录创建 Refresh Session。浏览器明文只进入 HttpOnly Cookie;BODY 模式 // 将明文返回给 App / 微信小程序客户端。 if (issueRefreshToken) { - String refreshToken = - refreshTokenService.issue(sessionId, userContext, refreshClientPolicy, - extraContext, securityVersion, response, accessToken, accessTokenTimeout); - loginResp.setRefreshExpiresIn( - refreshTokenService.getRefreshTimeout(refreshClientPolicy)); - if (RefreshTokenModeEnum.BODY - .equals(refreshTokenService.getMode(refreshClientPolicy))) { + String refreshToken = refreshTokenService.issue(sessionId, + new RefreshSessionPrincipalAdapter(userContext, extraContext), + refreshClientPolicy, securityVersion, response); + loginResp.setRefreshExpiresIn(refreshClientPolicy.refreshTokenTimeout()); + if (RefreshTokenModeEnum.BODY.equals(refreshClientPolicy.refreshTokenMode())) { loginResp.setRefreshToken(refreshToken); } } diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java index d04c39a31..136ac333b 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java @@ -21,12 +21,12 @@ import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import top.continew.admin.auth.model.SessionView; +import top.continew.starter.auth.refresh.token.model.SessionView; import top.continew.admin.auth.model.query.OnlineUserQuery; import top.continew.admin.auth.model.resp.OnlineUserResp; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.admin.auth.service.SessionInvalidationService; -import top.continew.admin.auth.service.SessionQueryService; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; +import top.continew.starter.auth.refresh.token.service.SessionQueryService; import top.continew.admin.common.context.UserContextHolder; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; diff --git a/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java b/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java index a8d540de3..716d35a75 100644 --- a/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java @@ -32,7 +32,7 @@ import top.continew.admin.common.enums.RoleCodeEnum; import top.continew.admin.common.model.dto.TenantDTO; import top.continew.admin.common.util.SecureUtils; -import top.continew.admin.auth.service.SessionInvalidationService; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; import top.continew.admin.system.mapper.DeptMapper; import top.continew.admin.system.mapper.LogMapper; import top.continew.admin.system.mapper.MessageMapper; diff --git a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java index 530990fe1..3082b235e 100644 --- a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java @@ -34,7 +34,7 @@ import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.CollUtils; -import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.util.UrlUtils; import top.continew.starter.storage.domain.model.resp.FileInfo; import top.continew.starter.storage.domain.model.resp.FilePartInfo; import top.continew.starter.storage.domain.model.resp.MultipartInitResp; @@ -78,12 +78,12 @@ public boolean save(FileInfo fileInfo) { file.setStorageId(storage.getId()); boolean saved = fileMapper.insert(file) > 0; fileInfo.setFileId(String.valueOf(file.getId())); - if (!URLUtils.isHttpUrl(fileInfo.getUrl())) { + if (!UrlUtils.isHttpUrl(fileInfo.getUrl())) { String prefix = storage.getUrlPrefix(); String url = URLUtil.normalize(prefix + fileInfo.getPath(), false, true); fileInfo.setUrl(url); if (StrUtil.isNotBlank(fileInfo.getThumbnailPath()) - && !URLUtils.isHttpUrl(fileInfo.getThumbnailPath())) { + && !UrlUtils.isHttpUrl(fileInfo.getThumbnailPath())) { fileInfo.setThumbnailPath( URLUtil.normalize(prefix + fileInfo.getThumbnailPath(), false, true)); } @@ -322,7 +322,7 @@ private FileDO getFileByUrl(String url) { LambdaQueryChainWrapper queryWrapper = fileMapper.lambdaQuery() .eq(FileDO::getName, StrUtil.subAfter(url, StringConstants.SLASH, true)); // 非 HTTP URL 场景 - if (!URLUtils.isHttpUrl(url)) { + if (!UrlUtils.isHttpUrl(url)) { return queryWrapper .eq(FileDO::getPath, StrUtil.prependIfMissing(url, StringConstants.SLASH)).one(); } diff --git a/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsConfigUtil.java b/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsConfigUtil.java index 96ed97545..54349353a 100644 --- a/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsConfigUtil.java +++ b/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsConfigUtil.java @@ -25,7 +25,7 @@ import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; import top.continew.admin.system.model.resp.SmsConfigResp; -import top.continew.starter.json.jackson.util.JSONUtils; +import top.continew.starter.json.jackson.util.JsonUtils; import java.util.Map; @@ -72,7 +72,7 @@ public static BaseConfig from(SmsConfigResp smsConfig) { configInfo.put("maxRetries", smsConfig.getMaxRetries()); } if (StrUtil.isNotBlank(smsConfig.getSupplierConfig())) { - configInfo.putAll(JSONUtils.toBean(smsConfig.getSupplierConfig(), Map.class)); + configInfo.putAll(JsonUtils.toBean(smsConfig.getSupplierConfig(), Map.class)); } return (BaseConfig) BeanUtil.toBean(configInfo, providerFactory.getConfigClass()); } diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/MenuController.java b/continew-system/src/main/java/top/continew/admin/system/controller/MenuController.java index 655d781a7..8968def26 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/MenuController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/MenuController.java @@ -32,7 +32,7 @@ import top.continew.admin.system.service.MenuService; import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.constant.StringConstants; -import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.util.UrlUtils; import top.continew.starter.core.util.validation.ValidationUtils; import top.continew.starter.extension.crud.annotation.CrudApi; import top.continew.starter.extension.crud.annotation.CrudRequestMapping; @@ -73,11 +73,11 @@ public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class MenuReq req = (MenuReq) args[0]; Boolean isExternal = ObjectUtil.defaultIfNull(req.getIsExternal(), false); String path = req.getPath(); - ValidationUtils.throwIf(Boolean.TRUE.equals(isExternal) && !URLUtils + ValidationUtils.throwIf(Boolean.TRUE.equals(isExternal) && !UrlUtils .isHttpUrl(path), "路由地址格式不正确,请以 http:// 或 https:// 开头"); // 非外链菜单参数修正 if (Boolean.FALSE.equals(isExternal)) { - ValidationUtils.throwIf(URLUtils.isHttpUrl(path), "路由地址格式不正确"); + ValidationUtils.throwIf(UrlUtils.isHttpUrl(path), "路由地址格式不正确"); req.setPath(StrUtil.isBlank(path) ? path : StrUtil.prependIfMissing(path, StringConstants.SLASH)); req.setName(StrUtil.removePrefix(req.getName(), StringConstants.SLASH)); diff --git a/continew-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java b/continew-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java index 684907f99..725098242 100644 --- a/continew-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java +++ b/continew-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java @@ -25,7 +25,7 @@ import top.continew.admin.system.validation.ValidationGroup; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.enums.BaseEnum; -import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.util.UrlUtils; import top.continew.starter.core.util.validation.ValidationUtils; import top.continew.starter.storage.common.constant.StorageConstant; @@ -47,7 +47,7 @@ public enum StorageTypeEnum implements BaseEnum { @Override public void validate(StorageReq req) { ValidationUtils.validate(req, ValidationGroup.Storage.Local.class); - ValidationUtils.throwIf(StrUtil.isNotBlank(req.getDomain()) && !URLUtils.isHttpUrl(req + ValidationUtils.throwIf(StrUtil.isNotBlank(req.getDomain()) && !UrlUtils.isHttpUrl(req .getDomain()), "访问路径格式不正确"); } diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java index e473640ef..4c3a0acaf 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java @@ -22,9 +22,9 @@ import lombok.Data; import top.continew.admin.common.base.model.entity.BaseDO; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; import top.continew.admin.system.enums.LogoutModeEnum; import top.continew.admin.system.enums.ReplacedRangeEnum; +import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; import java.io.Serial; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java index dd2f367f2..efd3c755b 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java @@ -19,17 +19,17 @@ import cn.sticki.spel.validator.constrain.SpelNotNull; import cn.sticki.spel.validator.jakarta.SpelValid; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; import top.continew.admin.system.enums.LogoutModeEnum; import top.continew.admin.system.enums.ReplacedRangeEnum; +import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java index 8ef29f7c4..08014dbbc 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java @@ -24,9 +24,9 @@ import top.continew.admin.common.config.excel.DictExcelProperty; import top.continew.admin.common.config.excel.ExcelDictConverter; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.auth.enums.RefreshTokenModeEnum; import top.continew.admin.system.enums.LogoutModeEnum; import top.continew.admin.system.enums.ReplacedRangeEnum; +import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; import top.continew.starter.excel.converter.ExcelListConverter; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java index 2e90c1c81..11001aae7 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/ClientServiceImpl.java @@ -23,11 +23,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import top.continew.admin.auth.adapter.ClientPolicyLockTargetResolver; import top.continew.admin.auth.model.query.OnlineUserQuery; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.admin.auth.service.SessionInvalidationService; -import top.continew.admin.auth.adapter.ClientPolicyLockTargetResolver; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.system.mapper.ClientMapper; import top.continew.admin.system.model.entity.ClientDO; @@ -35,6 +33,8 @@ import top.continew.admin.system.model.req.ClientReq; import top.continew.admin.system.model.resp.ClientResp; import top.continew.admin.system.service.ClientService; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.validation.CheckUtils; @@ -56,6 +56,7 @@ public class ClientServiceImpl private final OnlineUserService onlineUserService; private final SessionInvalidationService sessionInvalidationService; + @Override public void beforeCreate(ClientReq req) { req.setClientId(SecureUtil.md5(Base64.encode(IdUtil.fastSimpleUUID()) diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java index 955d5aa4d..138fc5fec 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java @@ -26,7 +26,7 @@ import org.springframework.transaction.annotation.Transactional; import top.continew.admin.auth.service.OnlineUserService; import top.continew.admin.auth.adapter.DeptUserPolicyLockTargetResolver; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.mapper.DeptMapper; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java index 195ee9445..22010ce69 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java @@ -52,8 +52,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.admin.auth.support.UserArgumentPolicyLockTargetResolver; -import top.continew.admin.auth.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.support.UserArgumentPolicyLockTargetResolver; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.constant.CacheConstants; import top.continew.admin.common.context.UserContext; diff --git a/continew-auth-refresh/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java b/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java similarity index 94% rename from continew-auth-refresh/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java rename to continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java index 4ccc33b94..e713edd37 100644 --- a/continew-auth-refresh/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java +++ b/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java @@ -22,9 +22,9 @@ import org.mockito.MockedStatic; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import top.continew.admin.auth.service.RefreshAccessTokenIssuer; -import top.continew.admin.auth.service.RefreshTokenService; -import top.continew.admin.auth.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; diff --git a/pom.xml b/pom.xml index f56419c4c..c82d677d5 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ top.continew.starter continew-starter - 2.16.1 + 2.17.0-SNAPSHOT top.continew.admin @@ -27,7 +27,6 @@ continew-server - continew-auth-refresh continew-system continew-plugin continew-common @@ -37,13 +36,6 @@ 4.2.0-SNAPSHOT - - - 11.0.0 - 3.6.0 - 4.9.4.0 - 3.5.0 - 1.9.0 @@ -63,13 +55,6 @@ ${revision}
- - - ${project.groupId} - continew-auth-refresh - ${revision} - - ${project.groupId} @@ -145,7 +130,6 @@ org.apache.maven.plugins maven-enforcer-plugin - ${maven-enforcer-plugin.version} org.codehaus.mojo @@ -211,7 +195,6 @@ org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle-plugin.version} com.puppycrawl.tools @@ -240,7 +223,6 @@ com.github.spotbugs spotbugs-maven-plugin - ${spotbugs.version} Max High From e115387c8ee60b642e380ef06e84eb9e8514c293 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Mon, 5 Oct 2026 11:52:37 +0800 Subject: [PATCH 2/5] =?UTF-8?q?style:=20=E6=96=B0=E5=A2=9E=20import=20?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E9=97=A8=E7=A6=81=E5=B9=B6=E6=B8=85=E9=9B=B6?= =?UTF-8?q?=E5=AD=98=E9=87=8F=E4=B9=B1=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - checkstyle 新增 CustomImportOrder 规则(组内字典序、组间空行、静态导入置顶、java 组收尾),与 Spotless 格式化产物对齐 - 此前四道门禁均无 import 顺序检查,存量乱序多年未被拦截;规则启用后规范化 413 处 import 分组/排序 - ClientServiceImpl 字段与 @Override 方法间补空行 Assisted-by: ZCode --- ...lSpringDocResponseOperationCustomizer.java | 6 ++--- .../exception/GlobalExceptionHandler.java | 3 ++- .../GlobalSaTokenExceptionHandler.java | 5 ++-- .../RefreshTokenExceptionHandler.java | 5 ++-- .../mybatis/MyBatisPlusMetaObjectHandler.java | 5 ++-- .../common/context/UserExtraContext.java | 5 ++-- .../model/req/CommonStatusUpdateReq.java | 2 +- .../controller/GeneratorController.java | 4 +-- .../generator/model/entity/FieldConfigDO.java | 4 +-- .../generator/model/entity/GenConfigDO.java | 6 ++--- .../generator/model/req/GenConfigReq.java | 6 ++--- .../generator/service/GeneratorService.java | 2 +- .../service/impl/GeneratorServiceImpl.java | 6 ++--- .../handler/SaCheckPermissionHandler.java | 4 +-- .../admin/open/model/entity/AppDO.java | 3 +-- .../continew/admin/open/model/req/AppReq.java | 4 +-- .../schedule/controller/JobController.java | 2 +- .../schedule/controller/JobLogController.java | 3 ++- .../schedule/model/query/JobLogQuery.java | 2 +- .../schedule/model/query/JobPageQuery.java | 2 +- .../admin/schedule/model/req/JobReq.java | 5 ++-- .../schedule/model/req/JobStatusReq.java | 2 +- .../schedule/model/req/JobTriggerReq.java | 2 +- .../admin/schedule/model/resp/JobResp.java | 1 + .../admin/tenant/api/TenantApiImpl.java | 6 ++--- ...PackageTenantPolicyLockTargetResolver.java | 2 +- .../tenant/config/DefaultTenantProvider.java | 3 ++- .../tenant/controller/TenantController.java | 3 ++- .../admin/tenant/mapper/PackageMapper.java | 2 +- .../admin/tenant/model/req/PackageReq.java | 4 +-- .../req/TenantAdminUserPwdUpdateReq.java | 2 +- .../admin/tenant/model/req/TenantReq.java | 8 +++--- .../service/impl/PackageServiceImpl.java | 6 ++--- .../service/impl/TenantServiceImpl.java | 7 +++-- .../config/DefaultTenantProviderTest.java | 12 ++++----- .../admin/config/log/DatabaseLogDao.java | 2 +- .../satoken/AccessTokenSecretValidator.java | 2 +- .../satoken/SaExtensionInterceptor.java | 6 ++--- .../config/satoken/SaTokenConfiguration.java | 2 +- .../admin/controller/CaptchaController.java | 8 +++--- .../continew/admin/job/NoticePublishJob.java | 3 +-- .../service/OnlineUserServiceImplTest.java | 10 +++---- .../satoken/SaExtensionInterceptorTest.java | 20 +++++++------- .../admin/auth/AbstractLoginHandler.java | 7 ++--- .../top/continew/admin/auth/LoginHandler.java | 5 ++-- .../ClientPolicyLockTargetResolver.java | 4 +-- .../DeptUserPolicyLockTargetResolver.java | 4 +-- .../adapter/RefreshAccessTokenIssuerImpl.java | 12 ++++----- .../adapter/RefreshClientPolicyMapper.java | 6 ++--- .../admin/auth/controller/AuthController.java | 8 +++--- .../auth/controller/OnlineUserController.java | 8 +++--- .../auth/controller/SessionController.java | 10 ++++--- .../auth/handler/AccountLoginHandler.java | 4 +-- .../admin/auth/handler/EmailLoginHandler.java | 5 ++-- .../admin/auth/handler/PhoneLoginHandler.java | 5 ++-- .../auth/handler/SocialLoginHandler.java | 7 +++-- .../admin/auth/model/req/AccountLoginReq.java | 2 +- .../admin/auth/model/req/EmailLoginReq.java | 4 +-- .../admin/auth/model/req/LoginReq.java | 4 +-- .../admin/auth/model/req/PhoneLoginReq.java | 2 +- .../admin/auth/model/req/SocialLoginReq.java | 2 +- .../admin/auth/model/resp/LoginResp.java | 2 +- .../admin/auth/service/AuthService.java | 4 +-- .../admin/auth/service/AuthTokenService.java | 7 ++--- .../auth/service/impl/AuthServiceImpl.java | 4 +-- .../service/impl/AuthTokenServiceImpl.java | 26 +++++++++---------- .../service/impl/OnlineUserServiceImpl.java | 4 +-- .../api/TenantDataApiForSystemImpl.java | 2 +- .../config/sms/SmsReadConfigDatabaseImpl.java | 2 -- .../system/controller/CommonController.java | 2 +- .../system/controller/DictController.java | 2 +- .../system/controller/FileController.java | 8 +++--- .../controller/FileRecycleController.java | 3 ++- .../system/controller/LogController.java | 5 ++-- .../controller/MultipartUploadController.java | 3 ++- .../system/controller/OptionController.java | 2 +- .../system/controller/RoleController.java | 6 ++--- .../system/controller/StorageController.java | 3 ++- .../system/controller/UserController.java | 6 ++--- .../controller/UserMessageController.java | 2 +- .../controller/UserProfileController.java | 6 ++--- .../system/enums/PasswordPolicyEnum.java | 2 +- .../admin/system/mapper/SmsConfigMapper.java | 2 +- .../admin/system/mapper/SmsLogMapper.java | 2 +- .../admin/system/mapper/user/UserMapper.java | 2 +- .../admin/system/model/entity/DeptDO.java | 2 +- .../admin/system/model/entity/DictDO.java | 2 +- .../admin/system/model/entity/DictItemDO.java | 2 +- .../admin/system/model/entity/MenuDO.java | 2 +- .../admin/system/model/entity/RoleDO.java | 2 +- .../system/model/entity/SmsConfigDO.java | 2 +- .../admin/system/model/entity/SmsLogDO.java | 2 +- .../admin/system/model/query/LogQuery.java | 2 +- .../admin/system/model/query/OptionQuery.java | 2 +- .../admin/system/model/query/UserQuery.java | 2 +- .../admin/system/model/req/ClientReq.java | 10 +++---- .../admin/system/model/req/DeptReq.java | 6 ++--- .../admin/system/model/req/DictItemReq.java | 6 ++--- .../admin/system/model/req/DictReq.java | 4 +-- .../admin/system/model/req/FileReq.java | 2 +- .../admin/system/model/req/MenuReq.java | 6 ++--- .../admin/system/model/req/MessageReq.java | 4 +-- .../model/req/MultipartUploadCreateReq.java | 6 ++--- .../admin/system/model/req/NoticeReq.java | 4 +-- .../admin/system/model/req/OptionReq.java | 4 +-- .../admin/system/model/req/RoleReq.java | 6 ++--- .../admin/system/model/req/SmsConfigReq.java | 8 +++--- .../admin/system/model/req/StorageReq.java | 8 +++--- .../req/user/UserBasicInfoUpdateReq.java | 6 ++--- .../model/req/user/UserEmailUpdateReq.java | 4 +-- .../system/model/req/user/UserImportReq.java | 4 +-- .../model/req/user/UserImportRowReq.java | 8 +++--- .../model/req/user/UserPasswordResetReq.java | 2 +- .../model/req/user/UserPasswordUpdateReq.java | 2 +- .../model/req/user/UserPhoneUpdateReq.java | 2 +- .../admin/system/model/req/user/UserReq.java | 12 ++++----- .../model/req/user/UserRoleUpdateReq.java | 2 +- .../admin/system/model/resp/DeptResp.java | 2 +- .../system/model/resp/SmsConfigResp.java | 2 +- .../admin/system/model/resp/SmsLogResp.java | 2 +- .../model/resp/notice/NoticeDetailResp.java | 2 +- .../model/resp/user/UserDetailResp.java | 2 +- .../system/service/FileRecycleService.java | 3 ++- .../admin/system/service/FileService.java | 5 ++-- .../admin/system/service/LogService.java | 3 ++- .../admin/system/service/RoleService.java | 2 +- .../admin/system/service/StorageService.java | 2 +- .../admin/system/service/UserService.java | 2 +- .../system/service/impl/DeptServiceImpl.java | 8 +++--- .../service/impl/DictItemServiceImpl.java | 2 +- .../service/impl/FileRecycleServiceImpl.java | 4 +-- .../system/service/impl/FileServiceImpl.java | 8 +++--- .../system/service/impl/LogServiceImpl.java | 4 +-- .../system/service/impl/MenuServiceImpl.java | 2 +- .../service/impl/MessageLogServiceImpl.java | 3 +-- .../impl/MultipartUploadServiceImpl.java | 2 +- .../service/impl/NoticeLogServiceImpl.java | 3 +-- .../service/impl/NoticeServiceImpl.java | 3 +-- .../system/service/impl/RoleServiceImpl.java | 2 +- .../service/impl/StorageServiceImpl.java | 2 +- .../service/impl/UserRoleServiceImpl.java | 2 +- .../system/service/impl/UserServiceImpl.java | 25 +++++++++--------- .../service/impl/UserSocialServiceImpl.java | 3 +-- .../admin/system/util/FileNameGenerator.java | 8 +++--- .../controller/SessionControllerTest.java | 20 +++++++------- style/ocn-checkstyle.xml | 7 +++++ 146 files changed, 342 insertions(+), 332 deletions(-) diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalSpringDocResponseOperationCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalSpringDocResponseOperationCustomizer.java index 55d1b0420..7f2a4fd90 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalSpringDocResponseOperationCustomizer.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalSpringDocResponseOperationCustomizer.java @@ -16,6 +16,9 @@ package top.continew.admin.common.config.doc; +import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper; +import static org.springdoc.core.utils.SpringDocAnnotationsUtils.extractSchema; + import cn.hutool.core.util.ClassUtil; import com.fasterxml.jackson.annotation.JsonView; import io.swagger.v3.oas.models.Components; @@ -37,9 +40,6 @@ import java.lang.reflect.Type; import java.util.Arrays; -import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper; -import static org.springdoc.core.utils.SpringDocAnnotationsUtils.extractSchema; - /** * 全局响应操作自定义器 *

diff --git a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java index f4186e7ab..dbed9f05d 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java @@ -19,7 +19,6 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.text.CharSequenceUtil; import com.fasterxml.jackson.databind.exc.InvalidFormatException; -import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.core.annotation.Order; @@ -40,6 +39,8 @@ import top.continew.starter.core.exception.BusinessException; import top.continew.starter.web.model.R; +import jakarta.servlet.http.HttpServletRequest; + /** * 全局异常处理器 * diff --git a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalSaTokenExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalSaTokenExceptionHandler.java index 5927c449c..5ddf23b3f 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalSaTokenExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalSaTokenExceptionHandler.java @@ -19,8 +19,6 @@ import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotPermissionException; import cn.dev33.satoken.exception.NotRoleException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; @@ -28,6 +26,9 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import top.continew.starter.web.model.R; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * 全局 SaToken 异常处理器 * diff --git a/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java index 18d4c8f21..ff8f1bb86 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/RefreshTokenExceptionHandler.java @@ -16,8 +16,6 @@ package top.continew.admin.common.config.exception; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -25,6 +23,9 @@ import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.starter.web.model.R; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** Refresh Token HTTP 协议异常处理器。 */ @Slf4j @Order(98) diff --git a/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java index a24d95a19..c0200a757 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java @@ -16,13 +16,12 @@ package top.continew.admin.common.config.mybatis; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; -import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.constant.GlobalConstants; +import top.continew.admin.common.context.UserContextHolder; import java.time.LocalDateTime; diff --git a/continew-common/src/main/java/top/continew/admin/common/context/UserExtraContext.java b/continew-common/src/main/java/top/continew/admin/common/context/UserExtraContext.java index c6a7331a4..d75fd4d60 100644 --- a/continew-common/src/main/java/top/continew/admin/common/context/UserExtraContext.java +++ b/continew-common/src/main/java/top/continew/admin/common/context/UserExtraContext.java @@ -16,17 +16,16 @@ package top.continew.admin.common.context; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.util.StrUtil; import cn.hutool.extra.servlet.JakartaServletUtil; -import jakarta.servlet.http.HttpServletRequest; import lombok.Data; import lombok.NoArgsConstructor; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.starter.core.util.ExceptionUtils; import top.continew.starter.core.util.IpUtils; import top.continew.starter.core.util.ServletUtils; +import jakarta.servlet.http.HttpServletRequest; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-common/src/main/java/top/continew/admin/common/model/req/CommonStatusUpdateReq.java b/continew-common/src/main/java/top/continew/admin/common/model/req/CommonStatusUpdateReq.java index 6eebdcb21..1727a9cc8 100644 --- a/continew-common/src/main/java/top/continew/admin/common/model/req/CommonStatusUpdateReq.java +++ b/continew-common/src/main/java/top/continew/admin/common/model/req/CommonStatusUpdateReq.java @@ -17,10 +17,10 @@ package top.continew.admin.common.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; import lombok.Data; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; /** diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/controller/GeneratorController.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/controller/GeneratorController.java index 2be117ca4..61528763a 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/controller/GeneratorController.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/controller/GeneratorController.java @@ -21,8 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -42,6 +40,8 @@ import top.continew.starter.extension.crud.model.resp.LabelValueResp; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import java.sql.SQLException; import java.util.List; diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/FieldConfigDO.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/FieldConfigDO.java index 597f37f96..df24fcd3e 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/FieldConfigDO.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/FieldConfigDO.java @@ -23,8 +23,6 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; @@ -32,6 +30,8 @@ import top.continew.admin.generator.enums.QueryTypeEnum; import top.continew.starter.core.constant.StringConstants; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/GenConfigDO.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/GenConfigDO.java index 775fffea3..377f65d90 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/GenConfigDO.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/entity/GenConfigDO.java @@ -23,9 +23,6 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; @@ -33,6 +30,9 @@ import top.continew.starter.core.constant.CharConstants; import top.continew.starter.core.util.StrUtils; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/req/GenConfigReq.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/req/GenConfigReq.java index 3123ea185..ab1ac3b8f 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/req/GenConfigReq.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/model/req/GenConfigReq.java @@ -17,13 +17,13 @@ package top.continew.admin.generator.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import lombok.Data; import top.continew.admin.generator.model.entity.FieldConfigDO; import top.continew.admin.generator.model.entity.GenConfigDO; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/GeneratorService.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/GeneratorService.java index 10040d9ff..1a0a54ad7 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/GeneratorService.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/GeneratorService.java @@ -16,7 +16,6 @@ package top.continew.admin.generator.service; -import jakarta.servlet.http.HttpServletResponse; import top.continew.admin.generator.model.entity.FieldConfigDO; import top.continew.admin.generator.model.entity.GenConfigDO; import top.continew.admin.generator.model.query.GenConfigQuery; @@ -25,6 +24,7 @@ import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.servlet.http.HttpServletResponse; import java.sql.SQLException; import java.util.List; diff --git a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/impl/GeneratorServiceImpl.java b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/impl/GeneratorServiceImpl.java index 66e63f074..6fb57e5ef 100644 --- a/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/impl/GeneratorServiceImpl.java +++ b/continew-plugin/continew-plugin-generator/src/main/java/top/continew/admin/generator/service/impl/GeneratorServiceImpl.java @@ -35,7 +35,6 @@ import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.DefaultObjectWrapperBuilder; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -58,14 +57,14 @@ import top.continew.starter.core.enums.BaseEnum; import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.util.CollUtils; +import top.continew.starter.core.util.FileUploadUtils; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.data.enums.DatabaseType; import top.continew.starter.data.util.MetaUtils; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; -import top.continew.starter.core.util.FileUploadUtils; -import javax.sql.DataSource; +import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.sql.SQLException; import java.util.ArrayList; @@ -79,6 +78,7 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import javax.sql.DataSource; /** * 代码生成业务实现 diff --git a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/handler/SaCheckPermissionHandler.java b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/handler/SaCheckPermissionHandler.java index b94c7cee2..ff347c732 100644 --- a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/handler/SaCheckPermissionHandler.java +++ b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/handler/SaCheckPermissionHandler.java @@ -16,6 +16,8 @@ package top.continew.admin.open.handler; +import static cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler._checkMethod; + import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface; import org.springframework.stereotype.Component; @@ -23,8 +25,6 @@ import java.lang.reflect.AnnotatedElement; -import static cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler._checkMethod; - /** * 重定义注解 SaCheckPermission 的处理器 * diff --git a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/entity/AppDO.java b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/entity/AppDO.java index 622f5f832..c4e358af5 100644 --- a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/entity/AppDO.java +++ b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/entity/AppDO.java @@ -16,11 +16,10 @@ package top.continew.admin.open.model.entity; -import top.continew.admin.common.constant.GlobalConstants; - import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.starter.encrypt.field.annotation.FieldEncrypt; diff --git a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/req/AppReq.java b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/req/AppReq.java index 779780cb1..78a8250f0 100644 --- a/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/req/AppReq.java +++ b/continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/model/req/AppReq.java @@ -17,12 +17,12 @@ package top.continew.admin.open.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobController.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobController.java index 93477a292..b4ed46b51 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobController.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobController.java @@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -45,6 +44,7 @@ import top.continew.starter.extension.crud.validation.CrudValidationGroup; import top.continew.starter.log.annotation.Log; +import jakarta.validation.Valid; import java.util.List; /** diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobLogController.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobLogController.java index f02fc183e..4083c3514 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobLogController.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/controller/JobLogController.java @@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -34,6 +33,8 @@ import top.continew.admin.schedule.service.JobLogService; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.validation.Valid; + /** * 任务日志 API * diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobLogQuery.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobLogQuery.java index c30e5bc36..b0b523fb9 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobLogQuery.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobLogQuery.java @@ -17,11 +17,11 @@ package top.continew.admin.schedule.model.query; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Size; import lombok.Data; import top.continew.admin.schedule.enums.JobExecuteStatusEnum; import top.continew.starter.validation.constraints.EnumValue; +import jakarta.validation.constraints.Size; import java.io.Serial; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobPageQuery.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobPageQuery.java index 884ba5c47..3127775d6 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobPageQuery.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/query/JobPageQuery.java @@ -17,10 +17,10 @@ package top.continew.admin.schedule.model.query; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; import lombok.Data; import org.hibernate.validator.constraints.Range; +import jakarta.validation.constraints.Min; import java.io.Serial; import java.io.Serializable; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobReq.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobReq.java index 1df5f851f..0c923582b 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobReq.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobReq.java @@ -17,8 +17,6 @@ package top.continew.admin.schedule.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.schedule.enums.JobBlockStrategyEnum; @@ -26,6 +24,9 @@ import top.continew.admin.schedule.enums.JobStatusEnum; import top.continew.admin.schedule.enums.JobTaskTypeEnum; import top.continew.admin.schedule.enums.JobTriggerTypeEnum; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobStatusReq.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobStatusReq.java index 450c36a88..1ed5777cf 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobStatusReq.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobStatusReq.java @@ -17,10 +17,10 @@ package top.continew.admin.schedule.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; import lombok.Data; import top.continew.admin.schedule.enums.JobStatusEnum; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobTriggerReq.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobTriggerReq.java index 05e92a346..6f99765d7 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobTriggerReq.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/req/JobTriggerReq.java @@ -17,9 +17,9 @@ package top.continew.admin.schedule.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; import lombok.Data; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/resp/JobResp.java b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/resp/JobResp.java index 1180f07ad..9e80e9d77 100644 --- a/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/resp/JobResp.java +++ b/continew-plugin/continew-plugin-schedule/src/main/java/top/continew/admin/schedule/model/resp/JobResp.java @@ -23,6 +23,7 @@ import top.continew.admin.schedule.enums.JobStatusEnum; import top.continew.admin.schedule.enums.JobTaskTypeEnum; import top.continew.admin.schedule.enums.JobTriggerTypeEnum; + import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/api/TenantApiImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/api/TenantApiImpl.java index 276f1f7d1..931bff0c7 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/api/TenantApiImpl.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/api/TenantApiImpl.java @@ -16,12 +16,12 @@ package top.continew.admin.tenant.api; -import top.continew.admin.common.config.TenantExtensionProperties; -import top.continew.admin.common.constant.GlobalConstants; -import top.continew.admin.common.enums.DisEnableStatusEnum; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import top.continew.admin.common.api.tenant.TenantApi; +import top.continew.admin.common.config.TenantExtensionProperties; +import top.continew.admin.common.constant.GlobalConstants; +import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.tenant.constant.TenantCacheConstants; import top.continew.admin.tenant.mapper.PackageMapper; import top.continew.admin.tenant.mapper.TenantMapper; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java index e115bc990..c8183758c 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/auth/PackageTenantPolicyLockTargetResolver.java @@ -18,9 +18,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import top.continew.admin.common.api.tenant.TenantApi; import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; -import top.continew.admin.common.api.tenant.TenantApi; import java.util.Collection; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/config/DefaultTenantProvider.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/config/DefaultTenantProvider.java index bb1468c5b..4cba94a1f 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/config/DefaultTenantProvider.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/config/DefaultTenantProvider.java @@ -17,7 +17,6 @@ package top.continew.admin.tenant.config; import cn.hutool.core.util.StrUtil; -import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import top.continew.admin.common.config.TenantExtensionProperties; import top.continew.admin.tenant.service.TenantService; @@ -26,6 +25,8 @@ import top.continew.starter.extension.tenant.config.TenantProvider; import top.continew.starter.extension.tenant.context.TenantContext; +import jakarta.servlet.http.HttpServletRequest; + /** * 默认租户提供者 * diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java index b83e797f5..d272ebb31 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/controller/TenantController.java @@ -19,7 +19,6 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; @@ -39,6 +38,8 @@ import top.continew.starter.extension.crud.enums.Api; import top.continew.starter.extension.tenant.util.TenantUtils; +import jakarta.validation.Valid; + /** * 租户管理 API * diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/PackageMapper.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/PackageMapper.java index 74c093af5..a162c874a 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/PackageMapper.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/mapper/PackageMapper.java @@ -16,8 +16,8 @@ package top.continew.admin.tenant.mapper; -import top.continew.starter.data.mapper.BaseMapper; import top.continew.admin.tenant.model.entity.PackageDO; +import top.continew.starter.data.mapper.BaseMapper; /** * 套餐 Mapper diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/PackageReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/PackageReq.java index 49a52e14e..3fd23d35a 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/PackageReq.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/PackageReq.java @@ -17,12 +17,12 @@ package top.continew.admin.tenant.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import java.io.Serial; import java.io.Serializable; import java.util.List; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantAdminUserPwdUpdateReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantAdminUserPwdUpdateReq.java index a7c85054c..b39976528 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantAdminUserPwdUpdateReq.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantAdminUserPwdUpdateReq.java @@ -17,9 +17,9 @@ package top.continew.admin.tenant.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; +import jakarta.validation.constraints.NotBlank; import java.io.Serializable; /** diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java index f61113129..b8b174290 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/model/req/TenantReq.java @@ -17,16 +17,16 @@ package top.continew.admin.tenant.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.starter.extension.crud.validation.CrudValidationGroup; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java index b67d5103c..44bbc2871 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/PackageServiceImpl.java @@ -16,15 +16,13 @@ package top.continew.admin.tenant.service.impl; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; -import top.continew.admin.tenant.auth.PackageTenantPolicyLockTargetResolver; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.admin.tenant.auth.PackageTenantPolicyLockTargetResolver; import top.continew.admin.tenant.mapper.PackageMapper; import top.continew.admin.tenant.model.entity.PackageDO; import top.continew.admin.tenant.model.query.PackageQuery; @@ -34,8 +32,10 @@ import top.continew.admin.tenant.service.PackageMenuService; import top.continew.admin.tenant.service.PackageService; import top.continew.admin.tenant.service.TenantService; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.starter.core.util.validation.CheckUtils; +import jakarta.annotation.Resource; import java.util.List; /** diff --git a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java index f94fd8169..465b080f6 100644 --- a/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java +++ b/continew-plugin/continew-plugin-tenant/src/main/java/top/continew/admin/tenant/service/impl/TenantServiceImpl.java @@ -16,8 +16,6 @@ package top.continew.admin.tenant.service.impl; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.extra.spring.SpringUtil; @@ -26,14 +24,13 @@ import me.ahoo.cosid.provider.IdGeneratorProvider; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; -import top.continew.starter.auth.refresh.token.support.TenantArgumentPolicyLockTargetResolver; import top.continew.admin.common.api.system.RoleApi; import top.continew.admin.common.api.system.RoleMenuApi; import top.continew.admin.common.api.tenant.TenantDataApi; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.config.TenantExtensionProperties; import top.continew.admin.common.constant.CacheConstants; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.enums.RoleCodeEnum; import top.continew.admin.common.model.dto.TenantDTO; @@ -47,6 +44,8 @@ import top.continew.admin.tenant.model.resp.TenantResp; import top.continew.admin.tenant.service.PackageService; import top.continew.admin.tenant.service.TenantService; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.support.TenantArgumentPolicyLockTargetResolver; import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.validation.CheckUtils; diff --git a/continew-plugin/continew-plugin-tenant/src/test/java/top/continew/admin/tenant/config/DefaultTenantProviderTest.java b/continew-plugin/continew-plugin-tenant/src/test/java/top/continew/admin/tenant/config/DefaultTenantProviderTest.java index 898a97be8..137066653 100644 --- a/continew-plugin/continew-plugin-tenant/src/test/java/top/continew/admin/tenant/config/DefaultTenantProviderTest.java +++ b/continew-plugin/continew-plugin-tenant/src/test/java/top/continew/admin/tenant/config/DefaultTenantProviderTest.java @@ -16,6 +16,12 @@ package top.continew.admin.tenant.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,12 +32,6 @@ import top.continew.admin.tenant.service.TenantService; import top.continew.starter.extension.tenant.context.TenantContext; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - /** * 默认租户提供者测试:验证刷新/退出时请求入口忽略残留租户头,而业务代码显式切换的 * 会话租户不被覆盖。 diff --git a/continew-server/src/main/java/top/continew/admin/config/log/DatabaseLogDao.java b/continew-server/src/main/java/top/continew/admin/config/log/DatabaseLogDao.java index 92d7d73c1..714ddcc9d 100644 --- a/continew-server/src/main/java/top/continew/admin/config/log/DatabaseLogDao.java +++ b/continew-server/src/main/java/top/continew/admin/config/log/DatabaseLogDao.java @@ -30,12 +30,12 @@ import org.springframework.http.HttpHeaders; import org.springframework.scheduling.annotation.Async; import top.continew.admin.auth.constant.AuthConstants; -import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.auth.enums.AuthTypeEnum; import top.continew.admin.auth.model.req.AccountLoginReq; import top.continew.admin.auth.model.req.EmailLoginReq; import top.continew.admin.auth.model.req.LoginReq; import top.continew.admin.auth.model.req.PhoneLoginReq; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.system.enums.LogStatusEnum; import top.continew.admin.system.mapper.LogMapper; import top.continew.admin.system.model.entity.LogDO; diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java b/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java index 960322266..70a8a409e 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/AccessTokenSecretValidator.java @@ -16,12 +16,12 @@ package top.continew.admin.config.satoken; -import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import jakarta.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java b/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java index 37aeb1869..f8721d50c 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/SaExtensionInterceptor.java @@ -19,22 +19,22 @@ import cn.dev33.satoken.fun.SaParamFunction; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.stp.StpUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; -import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.auth.constant.AuthConstants; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.context.UserExtraContext; import top.continew.admin.open.util.OpenApiUtils; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.starter.core.util.ServletUtils; import top.continew.starter.extension.tenant.context.TenantContextHolder; import top.continew.starter.json.jackson.util.JsonUtils; import top.continew.starter.web.model.R; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Objects; /** diff --git a/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java b/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java index a4e41d472..d83d6eb53 100644 --- a/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java +++ b/continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java @@ -37,10 +37,10 @@ import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationUtils; import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache; -import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.open.sign.OpenApiSignTemplate; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.exception.BusinessException; diff --git a/continew-server/src/main/java/top/continew/admin/controller/CaptchaController.java b/continew-server/src/main/java/top/continew/admin/controller/CaptchaController.java index 3d0921201..6e59f7446 100644 --- a/continew-server/src/main/java/top/continew/admin/controller/CaptchaController.java +++ b/continew-server/src/main/java/top/continew/admin/controller/CaptchaController.java @@ -31,10 +31,6 @@ import com.wf.captcha.base.Captcha; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.mail.MessagingException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.entity.SmsResponse; @@ -69,6 +65,10 @@ import top.continew.starter.validation.constraints.Mobile; import top.continew.starter.web.model.R; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import java.time.Duration; import java.time.Instant; import java.util.LinkedHashMap; diff --git a/continew-server/src/main/java/top/continew/admin/job/NoticePublishJob.java b/continew-server/src/main/java/top/continew/admin/job/NoticePublishJob.java index fd1ffdc50..c1a3c18b5 100644 --- a/continew-server/src/main/java/top/continew/admin/job/NoticePublishJob.java +++ b/continew-server/src/main/java/top/continew/admin/job/NoticePublishJob.java @@ -16,8 +16,6 @@ package top.continew.admin.job; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.collection.CollUtil; import cn.hutool.extra.spring.SpringUtil; import com.aizuda.snailjob.client.job.core.annotation.JobExecutor; @@ -27,6 +25,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.schedule.annotation.ConditionalOnEnabledScheduleJob; import top.continew.admin.system.enums.NoticeMethodEnum; import top.continew.admin.system.enums.NoticeStatusEnum; diff --git a/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java b/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java index 31e4f36f2..6382c2b85 100644 --- a/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java +++ b/continew-server/src/test/java/top/continew/admin/auth/service/OnlineUserServiceImplTest.java @@ -16,6 +16,11 @@ package top.continew.admin.auth.service; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import top.continew.admin.auth.model.query.OnlineUserQuery; @@ -32,11 +37,6 @@ import java.time.ZoneId; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - /** 在线用户以 Refresh Session 为事实源测试。 */ class OnlineUserServiceImplTest { diff --git a/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java b/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java index 70f2700c5..f4248eb6c 100644 --- a/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java +++ b/continew-server/src/test/java/top/continew/admin/config/satoken/SaExtensionInterceptorTest.java @@ -16,6 +16,15 @@ package top.continew.admin.config.satoken; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + import cn.dev33.satoken.SaManager; import cn.dev33.satoken.context.SaTokenContextForThreadLocal; import cn.dev33.satoken.servlet.model.SaRequestForServlet; @@ -33,20 +42,11 @@ import org.mockito.MockedStatic; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; +import top.continew.starter.auth.refresh.token.api.AccessSessionValidator; import top.continew.starter.extension.tenant.context.TenantContextHolder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - /** * SaExtensionInterceptor 短路路径线程本地清理测试。 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java index 1852f4672..64e79aff6 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java @@ -16,9 +16,6 @@ package top.continew.admin.auth; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import top.continew.admin.auth.model.req.LoginReq; import top.continew.admin.auth.model.resp.LoginResp; @@ -31,6 +28,10 @@ import top.continew.starter.core.util.validation.Validator; import top.continew.starter.extension.tenant.context.TenantContextHolder; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * 登录处理器基类 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java index 175cb77e5..16a2a7eaa 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java @@ -16,13 +16,14 @@ package top.continew.admin.auth; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import top.continew.admin.auth.enums.AuthTypeEnum; import top.continew.admin.auth.model.req.LoginReq; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.system.model.resp.ClientResp; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * 登录处理器 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java index 971bc386f..eccf373e5 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/ClientPolicyLockTargetResolver.java @@ -19,10 +19,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; -import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import top.continew.admin.system.mapper.ClientMapper; import top.continew.admin.system.model.entity.ClientDO; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import java.util.Collection; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java index efdf2d5e5..834f16823 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/DeptUserPolicyLockTargetResolver.java @@ -19,10 +19,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; -import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import top.continew.admin.system.mapper.user.UserMapper; import top.continew.admin.system.model.entity.user.UserDO; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTarget; +import top.continew.starter.auth.refresh.token.api.AuthPolicyLockTargetResolver; import java.util.Collection; diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java index 72b6cb531..8785558ba 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshAccessTokenIssuerImpl.java @@ -17,25 +17,25 @@ package top.continew.admin.auth.adapter; import cn.hutool.core.util.StrUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import top.continew.starter.auth.refresh.token.model.IssuedAccessToken; -import top.continew.starter.auth.refresh.token.model.RefreshSession; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.service.AuthTokenService; -import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; import top.continew.admin.common.api.tenant.TenantApi; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; import top.continew.admin.system.service.ClientService; import top.continew.admin.system.service.UserService; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.model.IssuedAccessToken; +import top.continew.starter.auth.refresh.token.model.RefreshSession; +import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; import top.continew.starter.core.exception.BusinessException; import top.continew.starter.extension.tenant.util.TenantUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.concurrent.atomic.AtomicReference; /** system 对刷新主体状态和 Access Token 签发的适配实现。 */ diff --git a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java index 213b540d2..54a290a35 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java +++ b/continew-system/src/main/java/top/continew/admin/auth/adapter/RefreshClientPolicyMapper.java @@ -16,12 +16,12 @@ package top.continew.admin.auth.adapter; -import top.continew.starter.auth.refresh.token.enums.LogoutReasonEnum; -import top.continew.starter.auth.refresh.token.enums.SessionReplacementScope; -import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; import top.continew.admin.system.enums.LogoutModeEnum; import top.continew.admin.system.enums.ReplacedRangeEnum; import top.continew.admin.system.model.resp.ClientResp; +import top.continew.starter.auth.refresh.token.enums.LogoutReasonEnum; +import top.continew.starter.auth.refresh.token.enums.SessionReplacementScope; +import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; /** * 系统客户端配置到认证会话策略的边界转换器。 diff --git a/continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java b/continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java index afe0f8325..b286cc833 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java +++ b/continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java @@ -22,9 +22,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; @@ -47,10 +44,13 @@ import top.continew.admin.system.model.resp.user.UserDetailResp; import top.continew.admin.system.service.UserService; import top.continew.starter.auth.justauth.AuthRequestFactory; -import top.continew.starter.log.enums.Include; import top.continew.starter.log.annotation.Log; +import top.continew.starter.log.enums.Include; import top.continew.starter.validation.constraints.EnumValue; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import java.util.List; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java b/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java index ad331fdbe..3ea9f048b 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java +++ b/continew-system/src/main/java/top/continew/admin/auth/controller/OnlineUserController.java @@ -23,7 +23,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -31,18 +30,19 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import top.continew.admin.auth.model.query.OnlineUserQuery; -import top.continew.starter.auth.refresh.token.model.SessionView; import top.continew.admin.auth.model.resp.OnlineUserResp; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; -import top.continew.starter.auth.refresh.token.service.SessionQueryService; import top.continew.admin.common.context.UserContextHolder; import top.continew.starter.auth.refresh.token.api.AuthSessionConstants; +import top.continew.starter.auth.refresh.token.model.SessionView; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; +import top.continew.starter.auth.refresh.token.service.SessionQueryService; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; import top.continew.starter.extension.tenant.context.TenantContextHolder; +import jakarta.validation.Valid; import java.util.Objects; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java b/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java index 4659c0c49..460977937 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java +++ b/continew-system/src/main/java/top/continew/admin/auth/controller/SessionController.java @@ -22,9 +22,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -42,6 +39,10 @@ import top.continew.starter.log.annotation.Log; import top.continew.starter.log.enums.Include; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; + /** Refresh Token 会话 API。 */ @Tag(name = "认证会话 API") @Log(module = "认证会话") @@ -66,7 +67,8 @@ public LoginResp refresh(@RequestBody(required = false) @Valid RefreshTokenReq r HttpServletRequest request, HttpServletResponse response) { // 四步安全钩子(解析 → 限流 → 来源校验 → 原子轮换)由 Starter 的 refresh 模板固化编排 RefreshIssueResult result = refreshTokenService.refresh(req == null ? null : req - .getRefreshToken(), request, response, session -> refreshAccessTokenIssuer.issue(session, + .getRefreshToken(), request, response, + session -> refreshAccessTokenIssuer.issue(session, request, response)); return LoginResp.builder().accessToken(result.getAccessToken()) .tokenType(result.getTokenType()).expiresIn(result.getExpiresIn()) diff --git a/continew-system/src/main/java/top/continew/admin/auth/handler/AccountLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/handler/AccountLoginHandler.java index 211e0869f..cc37c0590 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/handler/AccountLoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/handler/AccountLoginHandler.java @@ -20,8 +20,6 @@ import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.servlet.JakartaServletUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -39,6 +37,8 @@ import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.core.util.validation.ValidationUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.time.Duration; import java.time.LocalDateTime; diff --git a/continew-system/src/main/java/top/continew/admin/auth/handler/EmailLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/handler/EmailLoginHandler.java index 8ae0e0b3e..5dc673889 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/handler/EmailLoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/handler/EmailLoginHandler.java @@ -16,8 +16,6 @@ package top.continew.admin.auth.handler; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import top.continew.admin.auth.AbstractLoginHandler; import top.continew.admin.auth.enums.AuthTypeEnum; @@ -29,6 +27,9 @@ import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.util.validation.ValidationUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * 邮箱登录处理器 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/handler/PhoneLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/handler/PhoneLoginHandler.java index 5f1a7ed3b..d50f04177 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/handler/PhoneLoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/handler/PhoneLoginHandler.java @@ -16,8 +16,6 @@ package top.continew.admin.auth.handler; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import top.continew.admin.auth.AbstractLoginHandler; import top.continew.admin.auth.enums.AuthTypeEnum; @@ -29,6 +27,9 @@ import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.util.validation.ValidationUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * 手机号登录处理器 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/handler/SocialLoginHandler.java b/continew-system/src/main/java/top/continew/admin/auth/handler/SocialLoginHandler.java index d853b2283..36997a205 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/handler/SocialLoginHandler.java +++ b/continew-system/src/main/java/top/continew/admin/auth/handler/SocialLoginHandler.java @@ -16,8 +16,6 @@ package top.continew.admin.auth.handler; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; @@ -25,8 +23,6 @@ import cn.hutool.core.util.ReUtil; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; @@ -38,6 +34,7 @@ import top.continew.admin.auth.enums.AuthTypeEnum; import top.continew.admin.auth.model.req.SocialLoginReq; import top.continew.admin.auth.model.resp.LoginResp; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.enums.GenderEnum; @@ -57,6 +54,8 @@ import top.continew.starter.core.autoconfigure.application.ApplicationProperties; import top.continew.starter.core.util.validation.ValidationUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.time.LocalDateTime; import java.util.Collections; diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java index abb658a17..7612fa2b9 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/req/AccountLoginReq.java @@ -17,9 +17,9 @@ package top.continew.admin.auth.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java index 05616120c..775b8aab7 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/req/EmailLoginReq.java @@ -17,11 +17,11 @@ package top.continew.admin.auth.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/req/LoginReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/LoginReq.java index 76cc89287..7f1710aea 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/req/LoginReq.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/req/LoginReq.java @@ -19,11 +19,11 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import top.continew.admin.auth.enums.AuthTypeEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java index 355ad925c..34f151f6c 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/req/PhoneLoginReq.java @@ -17,11 +17,11 @@ package top.continew.admin.auth.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.starter.validation.constraints.Mobile; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/req/SocialLoginReq.java b/continew-system/src/main/java/top/continew/admin/auth/model/req/SocialLoginReq.java index 2c2d8b935..92eac89a8 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/req/SocialLoginReq.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/req/SocialLoginReq.java @@ -17,9 +17,9 @@ package top.continew.admin.auth.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java b/continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java index d8d37c2a8..690c1ee55 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java +++ b/continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java @@ -16,8 +16,8 @@ package top.continew.admin.auth.model.resp; -import io.swagger.v3.oas.annotations.media.Schema; import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java b/continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java index 538d856ed..e6ae2335c 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java @@ -16,12 +16,12 @@ package top.continew.admin.auth.service; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import top.continew.admin.auth.model.req.LoginReq; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.model.resp.RouteResp; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.List; /** diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java b/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java index d53d6c5bc..e36db6ace 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/AuthTokenService.java @@ -16,12 +16,13 @@ package top.continew.admin.auth.service; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import top.continew.starter.auth.refresh.token.model.RefreshSession; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; +import top.continew.starter.auth.refresh.token.model.RefreshSession; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * 统一访问令牌签发服务。 diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java index f4e610b25..32c46b4da 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java @@ -21,8 +21,6 @@ import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.TreeNodeConfig; import cn.hutool.core.lang.tree.TreeUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import top.continew.admin.auth.LoginHandler; @@ -45,6 +43,8 @@ import top.continew.starter.extension.crud.annotation.TreeField; import top.continew.starter.extension.crud.autoconfigure.CrudProperties; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java index 995628bb8..7625ef1db 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java @@ -16,33 +16,25 @@ package top.continew.admin.auth.service.impl; +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS; + import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.parameter.SaLoginParameter; import cn.hutool.core.bean.BeanUtil; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; -import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; -import top.continew.starter.auth.refresh.token.model.AuthSecurityVersion; -import top.continew.starter.auth.refresh.token.model.RefreshSession; -import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; import top.continew.admin.auth.adapter.RefreshClientPolicyMapper; import top.continew.admin.auth.adapter.RefreshSessionPrincipalAdapter; import top.continew.admin.auth.model.resp.LoginResp; import top.continew.admin.auth.service.AuthTokenService; -import top.continew.starter.auth.refresh.token.service.RefreshTokenService; -import top.continew.starter.auth.refresh.token.service.RefreshTokenService.LoginAttempt; -import top.continew.starter.auth.refresh.token.api.AuthSessionConstants; import top.continew.admin.common.api.tenant.TenantApi; import top.continew.admin.common.context.RoleContext; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.context.UserExtraContext; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; import top.continew.admin.system.model.entity.DeptDO; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.ClientResp; @@ -51,13 +43,23 @@ import top.continew.admin.system.service.OptionService; import top.continew.admin.system.service.RoleService; import top.continew.admin.system.service.UserService; +import top.continew.starter.auth.refresh.token.api.AuthSessionConstants; +import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.model.AuthSecurityVersion; +import top.continew.starter.auth.refresh.token.model.RefreshClientPolicy; +import top.continew.starter.auth.refresh.token.model.RefreshSession; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService.LoginAttempt; import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.extension.tenant.context.TenantContextHolder; import top.continew.starter.extension.tenant.util.TenantUtils; -import java.util.HashSet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -65,8 +67,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_EXPIRATION_DAYS; - /** * 统一 Access Token 签发实现。 * diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java index 136ac333b..fc4c6a6a7 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/OnlineUserServiceImpl.java @@ -21,13 +21,13 @@ import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import top.continew.starter.auth.refresh.token.model.SessionView; import top.continew.admin.auth.model.query.OnlineUserQuery; import top.continew.admin.auth.model.resp.OnlineUserResp; import top.continew.admin.auth.service.OnlineUserService; +import top.continew.admin.common.context.UserContextHolder; +import top.continew.starter.auth.refresh.token.model.SessionView; import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; import top.continew.starter.auth.refresh.token.service.SessionQueryService; -import top.continew.admin.common.context.UserContextHolder; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; import top.continew.starter.extension.tenant.context.TenantContextHolder; diff --git a/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java b/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java index 716d35a75..9a3f63789 100644 --- a/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/api/TenantDataApiForSystemImpl.java @@ -32,7 +32,6 @@ import top.continew.admin.common.enums.RoleCodeEnum; import top.continew.admin.common.model.dto.TenantDTO; import top.continew.admin.common.util.SecureUtils; -import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; import top.continew.admin.system.mapper.DeptMapper; import top.continew.admin.system.mapper.LogMapper; import top.continew.admin.system.mapper.MessageMapper; @@ -51,6 +50,7 @@ import top.continew.admin.system.service.FileService; import top.continew.admin.system.service.RoleMenuService; import top.continew.admin.system.service.UserRoleService; +import top.continew.starter.auth.refresh.token.service.SessionInvalidationService; import top.continew.starter.core.util.CollUtils; import top.continew.starter.extension.tenant.context.TenantContextHolder; import top.continew.starter.extension.tenant.util.TenantUtils; diff --git a/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsReadConfigDatabaseImpl.java b/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsReadConfigDatabaseImpl.java index b74c1751a..5cf422691 100644 --- a/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsReadConfigDatabaseImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/config/sms/SmsReadConfigDatabaseImpl.java @@ -18,11 +18,9 @@ import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; - import org.dromara.sms4j.core.datainterface.SmsReadConfig; import org.dromara.sms4j.provider.config.BaseConfig; import org.springframework.stereotype.Component; - import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.model.query.SmsConfigQuery; import top.continew.admin.system.model.resp.SmsConfigResp; diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java b/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java index 66aaad9df..c2259e91a 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java @@ -23,7 +23,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -48,6 +47,7 @@ import top.continew.starter.log.annotation.Log; import top.continew.starter.storage.domain.model.resp.FileInfo; +import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/DictController.java b/continew-system/src/main/java/top/continew/admin/system/controller/DictController.java index 2af97f447..b2889f005 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/DictController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/DictController.java @@ -22,8 +22,8 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; -import top.continew.admin.common.constant.CacheConstants; import top.continew.admin.common.base.controller.BaseController; +import top.continew.admin.common.constant.CacheConstants; import top.continew.admin.system.model.query.DictQuery; import top.continew.admin.system.model.req.DictReq; import top.continew.admin.system.model.resp.DictResp; diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java b/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java index f16ce0805..20d30f964 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java @@ -21,8 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -36,11 +34,11 @@ import top.continew.admin.common.base.controller.BaseController; import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.req.FileReq; -import top.continew.admin.system.model.resp.file.FileUploadConfigResp; -import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.admin.system.model.resp.file.FileDirCalcSizeResp; import top.continew.admin.system.model.resp.file.FileResp; import top.continew.admin.system.model.resp.file.FileStatisticsResp; +import top.continew.admin.system.model.resp.file.FileUploadConfigResp; +import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.admin.system.model.resp.file.FileUploadResp; import top.continew.admin.system.service.FileService; import top.continew.admin.system.service.StorageService; @@ -51,6 +49,8 @@ import top.continew.starter.log.annotation.Log; import top.continew.starter.storage.domain.model.resp.FileInfo; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.io.IOException; /** diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/FileRecycleController.java b/continew-system/src/main/java/top/continew/admin/system/controller/FileRecycleController.java index dce9983f6..e647a0d62 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/FileRecycleController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/FileRecycleController.java @@ -19,7 +19,6 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -33,6 +32,8 @@ import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.validation.Valid; + /** * 文件回收站管理 API * diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/LogController.java b/continew-system/src/main/java/top/continew/admin/system/controller/LogController.java index dfc508804..c3143b80f 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/LogController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/LogController.java @@ -22,8 +22,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -38,6 +36,9 @@ import top.continew.starter.extension.crud.model.resp.PageResp; import top.continew.starter.log.annotation.Log; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; + /** * 系统日志 API * diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/MultipartUploadController.java b/continew-system/src/main/java/top/continew/admin/system/controller/MultipartUploadController.java index 6b44e4e9e..af050e602 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/MultipartUploadController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/MultipartUploadController.java @@ -18,7 +18,6 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,6 +34,8 @@ import top.continew.admin.system.model.resp.file.MultipartUploadPartResp; import top.continew.admin.system.service.MultipartUploadService; +import jakarta.validation.Valid; + /** * 分片上传控制器 * diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/OptionController.java b/continew-system/src/main/java/top/continew/admin/system/controller/OptionController.java index ed4ea877a..6d94c2d67 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/OptionController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/OptionController.java @@ -20,7 +20,6 @@ import cn.dev33.satoken.annotation.SaMode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -34,6 +33,7 @@ import top.continew.admin.system.model.resp.OptionResp; import top.continew.admin.system.service.OptionService; +import jakarta.validation.Valid; import java.util.List; /** diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/RoleController.java b/continew-system/src/main/java/top/continew/admin/system/controller/RoleController.java index 5919980a7..de7ba5699 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/RoleController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/RoleController.java @@ -23,8 +23,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -37,8 +35,8 @@ import top.continew.admin.common.base.controller.BaseController; import top.continew.admin.system.model.query.RoleQuery; import top.continew.admin.system.model.query.RoleUserQuery; -import top.continew.admin.system.model.req.RoleReq; import top.continew.admin.system.model.req.RolePermissionUpdateReq; +import top.continew.admin.system.model.req.RoleReq; import top.continew.admin.system.model.resp.role.RoleDetailResp; import top.continew.admin.system.model.resp.role.RolePermissionResp; import top.continew.admin.system.model.resp.role.RoleResp; @@ -51,6 +49,8 @@ import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import java.util.List; /** diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/StorageController.java b/continew-system/src/main/java/top/continew/admin/system/controller/StorageController.java index 35612e7b5..68d663ae4 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/StorageController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/StorageController.java @@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -35,6 +34,8 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping; import top.continew.starter.extension.crud.enums.Api; +import jakarta.validation.Valid; + /** * 存储管理 API * diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/UserController.java b/continew-system/src/main/java/top/continew/admin/system/controller/UserController.java index c60eabfae..4da9127d5 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/UserController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/UserController.java @@ -21,9 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; @@ -51,6 +48,9 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping; import top.continew.starter.extension.crud.enums.Api; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.io.IOException; /** diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/UserMessageController.java b/continew-system/src/main/java/top/continew/admin/system/controller/UserMessageController.java index 03e40c78d..824464712 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/UserMessageController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/UserMessageController.java @@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -51,6 +50,7 @@ import top.continew.starter.extension.crud.model.resp.PageResp; import top.continew.starter.log.annotation.Log; +import jakarta.validation.Valid; import java.util.Collections; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/UserProfileController.java b/continew-system/src/main/java/top/continew/admin/system/controller/UserProfileController.java index 97e55739d..418a6b54d 100644 --- a/continew-system/src/main/java/top/continew/admin/system/controller/UserProfileController.java +++ b/continew-system/src/main/java/top/continew/admin/system/controller/UserProfileController.java @@ -16,12 +16,11 @@ package top.continew.admin.system.controller; +import cn.hutool.core.util.StrUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; @@ -37,7 +36,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import cn.hutool.core.util.StrUtil; import top.continew.admin.common.constant.CacheConstants; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.util.SecureUtils; @@ -57,6 +55,8 @@ import top.continew.starter.core.util.validation.ValidationUtils; import top.continew.starter.validation.constraints.EnumValue; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java b/continew-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java index f336e1dec..497411cdf 100644 --- a/continew-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java +++ b/continew-system/src/main/java/top/continew/admin/system/enums/PasswordPolicyEnum.java @@ -24,8 +24,8 @@ import cn.hutool.extra.spring.SpringUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; -import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.constant.GlobalConstants; +import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.service.OptionService; import top.continew.admin.system.service.UserPasswordHistoryService; diff --git a/continew-system/src/main/java/top/continew/admin/system/mapper/SmsConfigMapper.java b/continew-system/src/main/java/top/continew/admin/system/mapper/SmsConfigMapper.java index 2d25ad273..0d5fbc232 100644 --- a/continew-system/src/main/java/top/continew/admin/system/mapper/SmsConfigMapper.java +++ b/continew-system/src/main/java/top/continew/admin/system/mapper/SmsConfigMapper.java @@ -16,8 +16,8 @@ package top.continew.admin.system.mapper; -import top.continew.starter.data.mapper.BaseMapper; import top.continew.admin.system.model.entity.SmsConfigDO; +import top.continew.starter.data.mapper.BaseMapper; /** * 短信配置 Mapper diff --git a/continew-system/src/main/java/top/continew/admin/system/mapper/SmsLogMapper.java b/continew-system/src/main/java/top/continew/admin/system/mapper/SmsLogMapper.java index 5ed3c40d3..6427f1a20 100644 --- a/continew-system/src/main/java/top/continew/admin/system/mapper/SmsLogMapper.java +++ b/continew-system/src/main/java/top/continew/admin/system/mapper/SmsLogMapper.java @@ -16,8 +16,8 @@ package top.continew.admin.system.mapper; -import top.continew.starter.data.mapper.BaseMapper; import top.continew.admin.system.model.entity.SmsLogDO; +import top.continew.starter.data.mapper.BaseMapper; /** * 短信日志 Mapper diff --git a/continew-system/src/main/java/top/continew/admin/system/mapper/user/UserMapper.java b/continew-system/src/main/java/top/continew/admin/system/mapper/user/UserMapper.java index b0d91cdb2..ce74c274e 100644 --- a/continew-system/src/main/java/top/continew/admin/system/mapper/user/UserMapper.java +++ b/continew-system/src/main/java/top/continew/admin/system/mapper/user/UserMapper.java @@ -24,8 +24,8 @@ import top.continew.admin.common.base.mapper.DataPermissionMapper; import top.continew.admin.system.model.entity.user.UserDO; import top.continew.admin.system.model.resp.user.UserDetailResp; -import top.continew.starter.extension.datapermission.annotation.DataPermission; import top.continew.starter.encrypt.field.annotation.FieldEncrypt; +import top.continew.starter.extension.datapermission.annotation.DataPermission; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/DeptDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/DeptDO.java index 992c1d165..0af78423a 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/DeptDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/DeptDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.enums.DisEnableStatusEnum; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/DictDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/DictDO.java index 93122135d..0e3bddfb1 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/DictDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/DictDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.starter.extension.crud.annotation.DictModel; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.starter.extension.crud.annotation.DictModel; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/DictItemDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/DictItemDO.java index 90dc4f999..0d7a663c6 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/DictItemDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/DictItemDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.enums.DisEnableStatusEnum; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/MenuDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/MenuDO.java index 0f3f4b992..2ed52be47 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/MenuDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/MenuDO.java @@ -18,9 +18,9 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import top.continew.admin.common.base.model.entity.BaseDO; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.enums.MenuTypeEnum; -import top.continew.admin.common.base.model.entity.BaseDO; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/RoleDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/RoleDO.java index 10b1025a7..44d4c74d3 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/RoleDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/RoleDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.admin.common.enums.DataScopeEnum; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.enums.DataScopeEnum; import top.continew.starter.extension.crud.annotation.DictModel; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsConfigDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsConfigDO.java index b887985c2..6b68ffe97 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsConfigDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsConfigDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.base.model.entity.BaseDO; +import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.starter.encrypt.field.annotation.FieldEncrypt; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsLogDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsLogDO.java index f7ee3efef..2b0ee7346 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsLogDO.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/SmsLogDO.java @@ -18,8 +18,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import top.continew.admin.common.enums.SuccessFailureStatusEnum; import top.continew.admin.common.base.model.entity.BaseCreateDO; +import top.continew.admin.common.enums.SuccessFailureStatusEnum; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/query/LogQuery.java b/continew-system/src/main/java/top/continew/admin/system/model/query/LogQuery.java index ae833c4ec..e7c7b0f51 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/query/LogQuery.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/query/LogQuery.java @@ -17,10 +17,10 @@ package top.continew.admin.system.model.query; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Size; import lombok.Data; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.Size; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/query/OptionQuery.java b/continew-system/src/main/java/top/continew/admin/system/model/query/OptionQuery.java index 74035276f..c8b7c7756 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/query/OptionQuery.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/query/OptionQuery.java @@ -19,9 +19,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import top.continew.admin.system.enums.OptionCategoryEnum; -import top.continew.starter.validation.constraints.EnumValue; import top.continew.starter.data.annotation.Query; import top.continew.starter.data.enums.QueryType; +import top.continew.starter.validation.constraints.EnumValue; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/query/UserQuery.java b/continew-system/src/main/java/top/continew/admin/system/model/query/UserQuery.java index 50f823ae2..fde6b7133 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/query/UserQuery.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/query/UserQuery.java @@ -17,10 +17,10 @@ package top.continew.admin.system.model.query; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Size; import lombok.Data; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.Size; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java index efd3c755b..9792d7e0b 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java @@ -19,11 +19,6 @@ import cn.sticki.spel.validator.constrain.SpelNotNull; import cn.sticki.spel.validator.jakarta.SpelValid; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; @@ -31,6 +26,11 @@ import top.continew.admin.system.enums.ReplacedRangeEnum; import top.continew.starter.auth.refresh.token.enums.RefreshTokenModeEnum; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/DeptReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/DeptReq.java index 63d3b0ab9..065600235 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/DeptReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/DeptReq.java @@ -17,13 +17,13 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/DictItemReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/DictItemReq.java index 1504da408..596b1ab16 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/DictItemReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/DictItemReq.java @@ -17,13 +17,13 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/DictReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/DictReq.java index 259f34dfa..9aa33f52c 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/DictReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/DictReq.java @@ -17,12 +17,12 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/FileReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/FileReq.java index 34293dd5e..e8cc32e79 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/FileReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/FileReq.java @@ -17,10 +17,10 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/MenuReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/MenuReq.java index 0d9d060c3..740adde7f 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/MenuReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/MenuReq.java @@ -17,14 +17,14 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.enums.MenuTypeEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/MessageReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/MessageReq.java index 7bd7bb287..57c6bb946 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/MessageReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/MessageReq.java @@ -17,13 +17,13 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import top.continew.admin.system.enums.MessageTypeEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/MultipartUploadCreateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/MultipartUploadCreateReq.java index b426e37ac..85769d98a 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/MultipartUploadCreateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/MultipartUploadCreateReq.java @@ -17,11 +17,11 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Min; import lombok.Data; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; import java.util.Map; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/NoticeReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/NoticeReq.java index 3a6abd4a3..c1496b194 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/NoticeReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/NoticeReq.java @@ -21,13 +21,13 @@ import cn.sticki.spel.validator.constrain.SpelNotNull; import cn.sticki.spel.validator.jakarta.SpelValid; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.system.enums.NoticeScopeEnum; import top.continew.admin.system.enums.NoticeStatusEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/OptionReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/OptionReq.java index 50bfd08dd..120e0c4a8 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/OptionReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/OptionReq.java @@ -17,11 +17,11 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hibernate.validator.constraints.Length; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/RoleReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/RoleReq.java index 24901f250..b4e78c98d 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/RoleReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/RoleReq.java @@ -17,14 +17,14 @@ package top.continew.admin.system.model.req; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.enums.DataScopeEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/SmsConfigReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/SmsConfigReq.java index bc29e3556..63e005f7e 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/SmsConfigReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/SmsConfigReq.java @@ -16,15 +16,13 @@ package top.continew.admin.system.model.req; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - import io.swagger.v3.oas.annotations.media.Schema; - +import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.enums.DisEnableStatusEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java index 0003800d1..179b01293 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java @@ -20,10 +20,6 @@ import cn.sticki.spel.validator.jakarta.SpelValid; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; @@ -31,6 +27,10 @@ import top.continew.admin.system.enums.StorageTypeEnum; import top.continew.admin.system.validation.ValidationGroup; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserBasicInfoUpdateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserBasicInfoUpdateReq.java index f1e9d18ba..a6f20e798 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserBasicInfoUpdateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserBasicInfoUpdateReq.java @@ -17,13 +17,13 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.Data; import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.enums.GenderEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserEmailUpdateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserEmailUpdateReq.java index f80956692..d2e6f07b1 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserEmailUpdateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserEmailUpdateReq.java @@ -17,11 +17,11 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportReq.java index fb10d8937..08eff8331 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportReq.java @@ -17,12 +17,12 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Data; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.enums.ImportPolicyEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportRowReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportRowReq.java index 984ef64fd..5e8ddf8c2 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportRowReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserImportRowReq.java @@ -17,15 +17,15 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; -import top.continew.starter.validation.constraints.Mobile; import top.continew.starter.extension.crud.validation.CrudValidationGroup; +import top.continew.starter.validation.constraints.Mobile; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordResetReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordResetReq.java index 654cf8358..af09cb9da 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordResetReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordResetReq.java @@ -17,9 +17,9 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordUpdateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordUpdateReq.java index f8f642de9..17e3acf40 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordUpdateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPasswordUpdateReq.java @@ -17,9 +17,9 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPhoneUpdateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPhoneUpdateReq.java index 8ce9a4b30..981a97341 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPhoneUpdateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserPhoneUpdateReq.java @@ -17,11 +17,11 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.starter.validation.constraints.Mobile; +import jakarta.validation.constraints.NotBlank; import java.io.Serial; import java.io.Serializable; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserReq.java index 13be07001..4d6b9f4b3 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserReq.java @@ -17,19 +17,19 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.Length; import top.continew.admin.common.constant.RegexConstants; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.common.enums.GenderEnum; -import top.continew.starter.validation.constraints.Mobile; import top.continew.starter.extension.crud.validation.CrudValidationGroup; +import top.continew.starter.validation.constraints.Mobile; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.io.Serial; import java.io.Serializable; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserRoleUpdateReq.java b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserRoleUpdateReq.java index 948a60790..bf6d23bfa 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserRoleUpdateReq.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/req/user/UserRoleUpdateReq.java @@ -17,9 +17,9 @@ package top.continew.admin.system.model.req.user; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; import lombok.Data; +import jakarta.validation.constraints.NotEmpty; import java.io.Serial; import java.io.Serializable; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/DeptResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/DeptResp.java index f00ffefaa..9f4129a9f 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/DeptResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/DeptResp.java @@ -22,8 +22,8 @@ import lombok.Data; import top.continew.admin.common.base.model.resp.BaseDetailResp; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.starter.extension.crud.annotation.TreeField; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; +import top.continew.starter.extension.crud.annotation.TreeField; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsConfigResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsConfigResp.java index 813928fb8..4933b6fef 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsConfigResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsConfigResp.java @@ -20,10 +20,10 @@ import cn.idev.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import top.continew.admin.common.base.model.resp.BaseDetailResp; import top.continew.admin.common.config.excel.DictExcelProperty; import top.continew.admin.common.config.excel.ExcelDictConverter; import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.common.base.model.resp.BaseDetailResp; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; import top.continew.starter.security.mask.annotation.JsonMask; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsLogResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsLogResp.java index 8d15b65d0..16636c2c4 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsLogResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/SmsLogResp.java @@ -20,8 +20,8 @@ import cn.idev.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import top.continew.admin.common.enums.SuccessFailureStatusEnum; import top.continew.admin.common.base.model.resp.BaseResp; +import top.continew.admin.common.enums.SuccessFailureStatusEnum; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; import java.io.Serial; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/notice/NoticeDetailResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/notice/NoticeDetailResp.java index 31e113921..e02410d93 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/notice/NoticeDetailResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/notice/NoticeDetailResp.java @@ -20,9 +20,9 @@ import cn.idev.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import top.continew.admin.common.base.model.resp.BaseDetailResp; import top.continew.admin.common.config.excel.DictExcelProperty; import top.continew.admin.common.config.excel.ExcelDictConverter; -import top.continew.admin.common.base.model.resp.BaseDetailResp; import top.continew.admin.system.enums.NoticeScopeEnum; import top.continew.admin.system.enums.NoticeStatusEnum; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; diff --git a/continew-system/src/main/java/top/continew/admin/system/model/resp/user/UserDetailResp.java b/continew-system/src/main/java/top/continew/admin/system/model/resp/user/UserDetailResp.java index cd588266c..5d2e3f4df 100644 --- a/continew-system/src/main/java/top/continew/admin/system/model/resp/user/UserDetailResp.java +++ b/continew-system/src/main/java/top/continew/admin/system/model/resp/user/UserDetailResp.java @@ -34,9 +34,9 @@ import top.continew.admin.common.enums.GenderEnum; import top.continew.admin.system.model.resp.DeptResp; import top.continew.admin.system.service.DeptService; +import top.continew.starter.encrypt.field.annotation.FieldEncrypt; import top.continew.starter.excel.converter.ExcelBaseEnumConverter; import top.continew.starter.excel.converter.ExcelListConverter; -import top.continew.starter.encrypt.field.annotation.FieldEncrypt; import java.io.Serial; import java.time.LocalDateTime; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/FileRecycleService.java b/continew-system/src/main/java/top/continew/admin/system/service/FileRecycleService.java index 79ea7872a..163e1a5de 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/FileRecycleService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/FileRecycleService.java @@ -16,12 +16,13 @@ package top.continew.admin.system.service; -import jakarta.validation.Valid; import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.resp.file.FileResp; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.validation.Valid; + /** * 文件回收站业务接口 * diff --git a/continew-system/src/main/java/top/continew/admin/system/service/FileService.java b/continew-system/src/main/java/top/continew/admin/system/service/FileService.java index 4d1443d49..eae847150 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/FileService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/FileService.java @@ -16,18 +16,17 @@ package top.continew.admin.system.service; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.util.StrUtil; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.common.base.service.BaseService; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.system.model.entity.FileDO; import top.continew.admin.system.model.entity.StorageDO; import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.req.FileReq; -import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.admin.system.model.resp.file.FileResp; import top.continew.admin.system.model.resp.file.FileStatisticsResp; +import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.data.service.IService; import top.continew.starter.storage.domain.model.resp.FileInfo; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/LogService.java b/continew-system/src/main/java/top/continew/admin/system/service/LogService.java index b811e1ddc..d89a800c5 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/LogService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/LogService.java @@ -16,7 +16,6 @@ package top.continew.admin.system.service; -import jakarta.servlet.http.HttpServletResponse; import top.continew.admin.system.model.query.LogQuery; import top.continew.admin.system.model.resp.log.LogDetailResp; import top.continew.admin.system.model.resp.log.LogResp; @@ -24,6 +23,8 @@ import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.servlet.http.HttpServletResponse; + /** * 系统日志业务接口 * diff --git a/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java b/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java index 1e896702d..b6d344e29 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/RoleService.java @@ -20,8 +20,8 @@ import top.continew.admin.common.context.RoleContext; import top.continew.admin.system.model.entity.RoleDO; import top.continew.admin.system.model.query.RoleQuery; -import top.continew.admin.system.model.req.RoleReq; import top.continew.admin.system.model.req.RolePermissionUpdateReq; +import top.continew.admin.system.model.req.RoleReq; import top.continew.admin.system.model.resp.role.RoleDetailResp; import top.continew.admin.system.model.resp.role.RoleResp; import top.continew.starter.data.service.IService; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/StorageService.java b/continew-system/src/main/java/top/continew/admin/system/service/StorageService.java index a7b1e17f4..505360344 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/StorageService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/StorageService.java @@ -16,8 +16,8 @@ package top.continew.admin.system.service; -import top.continew.admin.common.model.req.CommonStatusUpdateReq; import top.continew.admin.common.base.service.BaseService; +import top.continew.admin.common.model.req.CommonStatusUpdateReq; import top.continew.admin.system.model.entity.StorageDO; import top.continew.admin.system.model.query.StorageQuery; import top.continew.admin.system.model.req.StorageReq; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/UserService.java b/continew-system/src/main/java/top/continew/admin/system/service/UserService.java index c4b982be7..47d4e1e0f 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/UserService.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/UserService.java @@ -16,7 +16,6 @@ package top.continew.admin.system.service; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.common.base.service.BaseService; import top.continew.admin.system.model.entity.user.UserDO; @@ -32,6 +31,7 @@ import top.continew.admin.system.model.resp.user.UserResp; import top.continew.starter.data.service.IService; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java index 138fc5fec..428888e95 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/DeptServiceImpl.java @@ -19,14 +19,12 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.admin.auth.service.OnlineUserService; import top.continew.admin.auth.adapter.DeptUserPolicyLockTargetResolver; -import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; +import top.continew.admin.auth.service.OnlineUserService; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.mapper.DeptMapper; @@ -37,11 +35,12 @@ import top.continew.admin.system.service.DeptService; import top.continew.admin.system.service.RoleDeptService; import top.continew.admin.system.service.UserService; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.data.enums.DatabaseType; import top.continew.starter.data.util.MetaUtils; -import javax.sql.DataSource; +import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,6 +48,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.sql.DataSource; /** * 部门业务实现 diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/DictItemServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/DictItemServiceImpl.java index b71eb9f52..cafb33517 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/DictItemServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/DictItemServiceImpl.java @@ -20,7 +20,6 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.StrUtil; -import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -39,6 +38,7 @@ import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import jakarta.annotation.PostConstruct; import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileRecycleServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileRecycleServiceImpl.java index fd7172cf8..a41d8df7e 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileRecycleServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileRecycleServiceImpl.java @@ -17,16 +17,16 @@ package top.continew.admin.system.service.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.constant.GlobalConstants; +import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.system.enums.FileTypeEnum; import top.continew.admin.system.mapper.FileMapper; import top.continew.admin.system.model.entity.FileDO; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java index dd9435a40..72e4c68c5 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java @@ -22,7 +22,6 @@ import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONUtil; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; @@ -31,19 +30,19 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.common.base.service.BaseServiceImpl; -import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.constant.GlobalConstants; +import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.enums.DisEnableStatusEnum; import top.continew.admin.system.enums.FileTypeEnum; +import top.continew.admin.system.enums.FileUploadProgressStatusEnum; import top.continew.admin.system.mapper.FileMapper; import top.continew.admin.system.model.entity.FileDO; import top.continew.admin.system.model.entity.StorageDO; import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.req.FileReq; -import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.admin.system.model.resp.file.FileResp; import top.continew.admin.system.model.resp.file.FileStatisticsResp; -import top.continew.admin.system.enums.FileUploadProgressStatusEnum; +import top.continew.admin.system.model.resp.file.FileUploadProgressResp; import top.continew.admin.system.service.FileService; import top.continew.admin.system.service.StorageService; import top.continew.admin.system.util.FileNameGenerator; @@ -60,6 +59,7 @@ import top.continew.starter.storage.domain.file.EnhancedMultipartFile; import top.continew.starter.storage.domain.model.resp.FileInfo; +import jakarta.annotation.Resource; import java.io.File; import java.io.IOException; import java.nio.file.Files; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/LogServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/LogServiceImpl.java index ef7304bb0..2e6f8b917 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/LogServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/LogServiceImpl.java @@ -23,7 +23,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -38,11 +37,12 @@ import top.continew.admin.system.service.LogService; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.data.util.QueryWrapperHelper; +import top.continew.starter.excel.util.ExcelUtils; import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.crud.model.resp.PageResp; -import top.continew.starter.excel.util.ExcelUtils; +import jakarta.servlet.http.HttpServletResponse; import java.time.LocalDateTime; import java.util.List; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/MenuServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/MenuServiceImpl.java index e8e36f54b..faf2917d9 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/MenuServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/MenuServiceImpl.java @@ -21,7 +21,6 @@ import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.util.StrUtil; import com.alicp.jetcache.anno.Cached; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -47,6 +46,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.tenant.context.TenantContextHolder; +import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Set; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageLogServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageLogServiceImpl.java index 91d1bb06a..37c6f91f7 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageLogServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageLogServiceImpl.java @@ -16,11 +16,10 @@ package top.continew.admin.system.service.impl; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.system.mapper.MessageLogMapper; import top.continew.admin.system.model.entity.MessageLogDO; import top.continew.admin.system.service.MessageLogService; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java index 646a86c61..2ff9b3f43 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java @@ -37,8 +37,8 @@ import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.storage.core.FileStorageService; -import top.continew.starter.storage.domain.model.resp.FileInfo; import top.continew.starter.storage.domain.model.req.MultipartUploadInitReq; +import top.continew.starter.storage.domain.model.resp.FileInfo; import top.continew.starter.storage.domain.model.resp.MultipartInitResp; import top.continew.starter.storage.domain.model.resp.MultipartUploadResp; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeLogServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeLogServiceImpl.java index 7056535ae..2f84db248 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeLogServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeLogServiceImpl.java @@ -16,12 +16,11 @@ package top.continew.admin.system.service.impl; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.system.mapper.NoticeLogMapper; import top.continew.admin.system.model.entity.NoticeLogDO; import top.continew.admin.system.service.NoticeLogService; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeServiceImpl.java index fe29dc9d4..66de9c8ed 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/NoticeServiceImpl.java @@ -16,14 +16,13 @@ package top.continew.admin.system.service.impl; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import top.continew.admin.common.base.service.BaseServiceImpl; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.system.enums.MessageTemplateEnum; import top.continew.admin.system.enums.MessageTypeEnum; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java index 2172bff64..2b5969e7c 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/RoleServiceImpl.java @@ -20,7 +20,6 @@ import cn.hutool.core.util.ObjectUtil; import com.alicp.jetcache.anno.CacheInvalidate; import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -50,6 +49,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.crud.model.resp.LabelValueResp; +import jakarta.annotation.Resource; import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java index d6950958f..e99d94f72 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java @@ -18,7 +18,6 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,6 +45,7 @@ import top.continew.starter.storage.strategy.impl.LocalStorageStrategy; import top.continew.starter.storage.strategy.impl.OssStorageStrategy; +import jakarta.annotation.Resource; import java.util.List; /** diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java index 1a898b293..dbd9ec2ff 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserRoleServiceImpl.java @@ -22,7 +22,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -44,6 +43,7 @@ import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.resp.PageResp; +import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import java.util.Set; diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java index 22010ce69..0ef00dfb1 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserServiceImpl.java @@ -16,7 +16,13 @@ package top.continew.admin.system.service.impl; -import top.continew.admin.common.constant.GlobalConstants; +import static top.continew.admin.system.enums.ImportPolicyEnum.EXIT; +import static top.continew.admin.system.enums.ImportPolicyEnum.SKIP; +import static top.continew.admin.system.enums.ImportPolicyEnum.UPDATE; +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_ALLOW_CONTAIN_USERNAME; +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_MIN_LENGTH; +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_REPETITION_TIMES; +import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_REQUIRE_SYMBOLS; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; @@ -39,8 +45,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.ahoo.cosid.IdGenerator; @@ -52,10 +56,9 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.auth.service.OnlineUserService; -import top.continew.starter.auth.refresh.token.support.UserArgumentPolicyLockTargetResolver; -import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; import top.continew.admin.common.base.service.BaseServiceImpl; import top.continew.admin.common.constant.CacheConstants; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.common.context.UserContext; import top.continew.admin.common.context.UserContextHolder; import top.continew.admin.common.enums.DisEnableStatusEnum; @@ -86,6 +89,8 @@ import top.continew.admin.system.service.UserRoleService; import top.continew.admin.system.service.UserService; import top.continew.admin.system.service.UserSocialService; +import top.continew.starter.auth.refresh.token.api.AuthPolicyWriteLocked; +import top.continew.starter.auth.refresh.token.support.UserArgumentPolicyLockTargetResolver; import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.exception.BusinessException; @@ -99,6 +104,8 @@ import top.continew.starter.storage.core.FileStorageService; import top.continew.starter.storage.domain.model.resp.FileInfo; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.Duration; import java.time.LocalDateTime; @@ -114,14 +121,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static top.continew.admin.system.enums.ImportPolicyEnum.EXIT; -import static top.continew.admin.system.enums.ImportPolicyEnum.SKIP; -import static top.continew.admin.system.enums.ImportPolicyEnum.UPDATE; -import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_ALLOW_CONTAIN_USERNAME; -import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_MIN_LENGTH; -import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_REPETITION_TIMES; -import static top.continew.admin.system.enums.PasswordPolicyEnum.PASSWORD_REQUIRE_SYMBOLS; - /** * 用户业务实现 * diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserSocialServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserSocialServiceImpl.java index 8ab7ba40f..6a2e890c7 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/UserSocialServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/UserSocialServiceImpl.java @@ -16,13 +16,12 @@ package top.continew.admin.system.service.impl; -import top.continew.admin.common.constant.GlobalConstants; - import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import me.zhyd.oauth.model.AuthUser; import org.springframework.stereotype.Service; +import top.continew.admin.common.constant.GlobalConstants; import top.continew.admin.system.enums.SocialSourceEnum; import top.continew.admin.system.mapper.user.UserSocialMapper; import top.continew.admin.system.model.entity.user.UserSocialDO; diff --git a/continew-system/src/main/java/top/continew/admin/system/util/FileNameGenerator.java b/continew-system/src/main/java/top/continew/admin/system/util/FileNameGenerator.java index 282282fa6..8aae04973 100644 --- a/continew-system/src/main/java/top/continew/admin/system/util/FileNameGenerator.java +++ b/continew-system/src/main/java/top/continew/admin/system/util/FileNameGenerator.java @@ -16,17 +16,15 @@ package top.continew.admin.system.util; -import java.util.List; - -import lombok.extern.slf4j.Slf4j; - import cn.hutool.core.util.StrUtil; - +import lombok.extern.slf4j.Slf4j; import top.continew.admin.system.enums.FileTypeEnum; import top.continew.admin.system.mapper.FileMapper; import top.continew.admin.system.model.entity.FileDO; import top.continew.starter.core.util.CollUtils; +import java.util.List; + /** * 文件名生成工具类 * diff --git a/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java b/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java index e713edd37..b846f70ad 100644 --- a/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java +++ b/continew-system/src/test/java/top/continew/admin/auth/controller/SessionControllerTest.java @@ -16,16 +16,6 @@ package top.continew.admin.auth.controller; -import cn.dev33.satoken.stp.StpUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; -import top.continew.starter.auth.refresh.token.service.RefreshTokenService; -import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; - import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; @@ -35,6 +25,16 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import cn.dev33.satoken.stp.StpUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import top.continew.starter.auth.refresh.token.exception.RefreshTokenException; +import top.continew.starter.auth.refresh.token.service.RefreshAccessTokenIssuer; +import top.continew.starter.auth.refresh.token.service.RefreshTokenService; + /** logout Cookie 安全边界测试。 */ class SessionControllerTest { diff --git a/style/ocn-checkstyle.xml b/style/ocn-checkstyle.xml index 92aaedcbd..dcb39eef4 100644 --- a/style/ocn-checkstyle.xml +++ b/style/ocn-checkstyle.xml @@ -52,6 +52,13 @@ + + + + + + + From c8c2f7460f06cb149932735f48786170687d1a5d Mon Sep 17 00:00:00 2001 From: Charles7c Date: Mon, 5 Oct 2026 23:58:52 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(system):=20=E7=AB=99=E5=86=85=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=AE=9A=E5=90=91=E6=8E=A8=E9=80=81=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E4=BC=9A=E8=AF=9D=E4=B8=8E=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readMessage 与 add 均改用 sendMessageToAll 并遍历用户全部在线令牌: 多会话并发时仅取最新令牌会遗漏其余会话,同一令牌多标签页时仅推单条连接。 Assisted-by: ZCode --- .../admin/system/service/impl/MessageServiceImpl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java index 55b2fd637..755c76b26 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java @@ -82,9 +82,11 @@ public void readMessage(List ids, Long userId) { messageLogService.addWithUserId(CollUtil.isNotEmpty(ids) ? CollUtil.intersection(unreadIds, ids).stream().toList() : unreadIds, userId); - WebSocketUtils.sendMessage(StpUtil.getTokenValueByLoginId(userId), String.valueOf(baseMapper - .selectUnreadListByUserId(userId) - .size())); + // 遍历该用户全部在线令牌推送未读数:多会话并发时仅取最新令牌会遗漏其余会话; + // sendMessageToAll 覆盖同一令牌下多标签页的全部连接 + String unreadCount = String.valueOf(baseMapper.selectUnreadListByUserId(userId).size()); + StpUtil.getTokenValueListByLoginId(userId) + .forEach(token -> WebSocketUtils.sendMessageToAll(token, unreadCount)); } @Override @@ -123,7 +125,7 @@ public void add(MessageReq req, List userIdList) { // 放到 ForkJoinPool 公共池上既拿不到事务与租户上下文,也会阻塞公共池 userIdList.forEach(userId -> { List tokenList = StpUtil.getTokenValueListByLoginId(userId); - tokenList.forEach(token -> WebSocketUtils.sendMessage(token, "1")); + tokenList.forEach(token -> WebSocketUtils.sendMessageToAll(token, "1")); }); return; } From 534162a36d8ff4f73346d281c042247c43e40396 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Tue, 6 Oct 2026 12:40:45 +0800 Subject: [PATCH 4/5] =?UTF-8?q?build:=20ContiNew=20Starter=20=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E8=87=B3=202.17.0=20=E6=AD=A3=E5=BC=8F=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 父 POM 由 2.17.0-SNAPSHOT 切换为 2.17.0(Maven Central 已发布) - README 同步 Starter 版本徽章与技术栈版本表 Assisted-by: ZCode --- README.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3885ca0bb..7aa06693a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Current Version -ContiNew Starter +ContiNew Starter Spring Boot @@ -251,7 +251,7 @@ public class DeptController extends BaseControllerArco Design | 2.57.0 | 字节跳动推出的前端 UI 框架,年轻化的色彩和组件设计。 | | TypeScript | 5.0.4 | TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。 | | Vite | 5.1.5 | 下一代的前端工具链,为开发提供极速响应。 | -| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.17.0-SNAPSHOT | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。 | +| [ContiNew Starter](https://github.com/continew-org/continew-starter) | 2.17.0 | ContiNew Starter 包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。 | | Spring Boot | 3.5.16 | 简化 Spring 应用的初始搭建和开发过程,基于“约定优于配置”的理念,使开发人员不再需要定义样板化的配置。(Spring Boot 3.0 开始,要求 Java 17 作为最低版本) | | Undertow | 2.3.18.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 | | Sa-Token + JWT | 1.45.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 | diff --git a/pom.xml b/pom.xml index c82d677d5..9e00463dc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ top.continew.starter continew-starter - 2.17.0-SNAPSHOT + 2.17.0 top.continew.admin From e3f46512fc516126ab6479962713d121af8383a8 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Wed, 7 Oct 2026 09:33:41 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(auth):=20=E6=98=BE=E5=BC=8F=E7=A9=BA?= =?UTF-8?q?=E6=8A=A4=E5=8D=AB=E6=9B=BF=E4=BB=A3=20CheckUtils/ValidationUti?= =?UTF-8?q?ls.throwIfNull=EF=BC=8C=E6=B6=88=E9=99=A4=20Sonar=20S2259=20?= =?UTF-8?q?=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sonar 无法把 starter 的 throwIfNull 识别为空护卫,PR #231 新代码上报告两处 S2259(currentClient/client 首次解引用)。改为显式 if-null 抛同类异常,行为不变; 顺带给 loginHandler 补上缺失的空护卫(认证类型无对应处理器时由 NPE 500 变为 400)。 Assisted-by: ZCode --- .../continew/admin/auth/service/impl/AuthServiceImpl.java | 8 +++++++- .../admin/auth/service/impl/AuthTokenServiceImpl.java | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java index 32c46b4da..e13d1eb75 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java @@ -39,6 +39,7 @@ import top.continew.admin.system.service.ClientService; import top.continew.admin.system.service.MenuService; import top.continew.admin.system.service.RoleService; +import top.continew.starter.core.exception.BadRequestException; import top.continew.starter.core.util.validation.ValidationUtils; import top.continew.starter.extension.crud.annotation.TreeField; import top.continew.starter.extension.crud.autoconfigure.CrudProperties; @@ -71,13 +72,18 @@ public LoginResp login(LoginReq req, HttpServletRequest request, HttpServletResp AuthTypeEnum authType = req.getAuthType(); // 校验客户端 ClientResp client = clientService.getByClientId(req.getClientId()); - ValidationUtils.throwIfNull(client, "客户端不存在"); + if (client == null) { + throw new BadRequestException("客户端不存在"); + } ValidationUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(client.getStatus()), "客户端已禁用"); ValidationUtils.throwIf(!client.getAuthType().contains(authType.getValue()), "该客户端暂未授权 [{}] 认证", authType .getDescription()); // 获取处理器 LoginHandler loginHandler = loginHandlerFactory.getHandler(authType); + if (loginHandler == null) { + throw new BadRequestException("不支持的认证类型 [" + authType.getDescription() + "]"); + } // 登录前置处理 loginHandler.preLogin(req, client, request); // 登录 diff --git a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java index 7625ef1db..6f86afa0c 100644 --- a/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthTokenServiceImpl.java @@ -268,7 +268,9 @@ private void checkUserStatus(UserDO user, boolean refreshRequest) { private LoginState reloadLoginState(UserDO authenticatedUser, ClientResp authenticatedClient, Long tenantId) { ClientResp currentClient = clientService.getByClientId(authenticatedClient.getClientId()); - CheckUtils.throwIfNull(currentClient, "客户端不存在"); + if (currentClient == null) { + throw new BusinessException("客户端不存在"); + } CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, currentClient.getStatus(), "客户端已禁用"); if (!this.isSameSecurityPolicy(authenticatedClient, currentClient)) {