feat(system/client): 添加双token认证配置#212
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough为认证系统添加刷新令牌支持:数据库变更增加刷新令牌字段;新增单/双令牌响应 DTO;引入刷新令牌端点与服务实现;认证登录逻辑委托新建的 LoginHandler 构建响应并管理刷新令牌生命周期。 Changes
Sequence Diagram(s)sequenceDiagram
participant Client as 客户端
participant AuthCtrl as AuthController
participant AuthSvc as AuthService
participant LoginHandler as LoginHandler
participant SaTempUtil as SaTempUtil
participant StpUtil as StpUtil
Client->>AuthCtrl: POST /login (凭证)
AuthCtrl->>AuthSvc: login(...)
AuthSvc->>LoginHandler: buildLoginResp(loginParameter, userContext, client)
alt 启用刷新令牌
LoginHandler->>StpUtil: login() 创建访问令牌
LoginHandler->>StpUtil: getTokenValue() 获取访问令牌值
LoginHandler->>SaTempUtil: create/save 刷新令牌
LoginHandler-->>AuthSvc: DoubleTokenLoginResp(access, refresh, expiries)
else 未启用刷新令牌
LoginHandler->>StpUtil: login() 创建访问令牌
LoginHandler-->>AuthSvc: SingleTokenLoginResp(token, expiresIn)
end
AuthSvc-->>AuthCtrl: LoginResp
AuthCtrl-->>Client: HTTP 200 + LoginResp
sequenceDiagram
participant Client as 客户端
participant AuthCtrl as AuthController
participant AuthSvc as AuthService
participant SaTempUtil as SaTempUtil
participant LoginHandler as LoginHandler
participant StpUtil as StpUtil
Client->>AuthCtrl: POST /refreshToken (refreshToken=xxx)
AuthCtrl->>AuthSvc: refreshToken(xxx)
AuthSvc->>SaTempUtil: parseToken(xxx)
alt 刷新令牌有效
SaTempUtil-->>AuthSvc: SaLoginParameter / extraData (含 LOGIN_USER)
AuthSvc->>LoginHandler: buildLoginResp(loginParameter, userContext, client)
LoginHandler-->>AuthSvc: 新 LoginResp
AuthSvc->>StpUtil: kickoutByTokenValue(旧访问令牌) (可选)
AuthSvc->>SaTempUtil: deleteToken(xxx)
AuthSvc-->>AuthCtrl: 新 LoginResp
else 无效或过期
AuthSvc-->>AuthCtrl: 抛出 BusinessException
end
AuthCtrl-->>Client: HTTP 200 / 4xx
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java (1)
21-45:⚠️ Potential issue | 🟡 MinorSwagger/OpenAPI 文档不完整,需要处理多态响应类型。
将
token字段下沉到SingleTokenLoginResp/DoubleTokenLoginResp子类后,存在一个文档层面的问题:AuthController的login()/refreshToken()方法声明返回类型为LoginResp基类,导致 Swagger 生成的响应 Schema 仅包含tenantId字段,调用方通过 OpenAPI 文档无法看到实际响应中的token/accessToken/refreshToken等字段。建议在
LoginResp基类的@Schema注解中显式声明多态类型,例如:`@Schema`(description = "登录响应参数", oneOf = {SingleTokenLoginResp.class, DoubleTokenLoginResp.class})或在 Spring Boot 3.x + SpringDoc OpenAPI 环境中使用
@Schema(discriminatorProperty = "...")结合子类标记,确保 API 文档准确反映实际响应结构。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java` around lines 21 - 45, LoginResp's OpenAPI schema lacks the subclass token fields because AuthController.login()/refreshToken() return the LoginResp base type; update the LoginResp class to declare its polymorphic subtypes so Swagger sees the actual response shapes — add a `@Schema` on LoginResp referencing oneOf = {SingleTokenLoginResp.class, DoubleTokenLoginResp.class} or use `@Schema`(discriminatorProperty = "...") with discriminator values on the subclasses (SingleTokenLoginResp, DoubleTokenLoginResp) so the generated docs include token/accessToken/refreshToken fields for the endpoints.
♻️ Duplicate comments (1)
continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java (1)
26-53:⚠️ Potential issue | 🟡 Minor同样需要
@EqualsAndHashCode(callSuper = true)与
SingleTokenLoginResp同样问题:@Data不调用父类的equals/hashCode,父类LoginResp的字段(如tenantId)会被忽略。`@Data` +@EqualsAndHashCode(callSuper = true) `@SuperBuilder` public class DoubleTokenLoginResp extends LoginResp {说明:Swagger
example中给出的 JWT 字符串为文档示例占位,非真实凭据,可忽略静态扫描告警。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java` around lines 26 - 53, DoubleTokenLoginResp (like SingleTokenLoginResp) currently uses Lombok's `@Data` which generates equals/hashCode without calling the superclass, causing LoginResp fields (e.g., tenantId) to be ignored; fix by adding Lombok's `@EqualsAndHashCode`(callSuper = true) to the DoubleTokenLoginResp class declaration so equals/hashCode include LoginResp fields (ensure the import/annotation is added alongside `@Data/`@SuperBuilder).
🧹 Nitpick comments (3)
continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql (1)
1-21: PostgreSQL changeset 的若干小问题与幂等性建议。
- Line 8 缩进与 line 7 不一致(两空格 vs 四空格),建议统一。
- Line 17
INSERT INTO "sys_client"未带 schema 前缀,而 line 6ALTER TABLE "public"."sys_client"带了,建议保持一致,避免在非默认search_path场景下出现歧义。id = 2为硬编码主键,若目标环境已存在该行(例如此前手工初始化/从其它环境同步),该 INSERT 将失败并导致整条 changeset 回滚(包含 ALTER)。建议改为幂等写法。- 整个 changeset 未提供
-- rollback指令,Liquibase 回滚时只能依赖自动推断,对 DDL + DML 的组合不可靠。建议的 diff
-- liquibase formatted sql -- changeset luoqiz:4.2.0-1 -- comment sys_client 客户端表更新 -- sys_client 添加双token列 ALTER TABLE "public"."sys_client" ADD COLUMN "is_enable_refresh_token" bool NOT NULL DEFAULT false, - ADD COLUMN "refresh_token_timeout" int8 DEFAULT 2592000; + ADD COLUMN "refresh_token_timeout" int8 DEFAULT 2592000; COMMENT ON COLUMN "public"."sys_client"."is_enable_refresh_token" IS '是否启用refresh token (true: 启用; false: 禁用)'; COMMENT ON COLUMN "public"."sys_client"."refresh_token_timeout" IS 'Refresh token有效期(单位:秒; 值必须大于0,否则取token的有效时长)'; -- 初始化客户端数据 -INSERT INTO "sys_client" +INSERT INTO "public"."sys_client" ("id", "client_id", "client_type", "auth_type", "active_timeout", "timeout", "status", "create_user", "create_time", "is_enable_refresh_token", "refresh_token_timeout") VALUES (2, 'ef51c9a3e9046c4f2ea45142c8a8344b', 'XCX', '["ACCOUNT", "EMAIL", "PHONE", "SOCIAL"]', 1800, 86400, 1, 1, - NOW(), true, 2592000); + NOW(), true, 2592000) +ON CONFLICT ("id") DO NOTHING; + +-- rollback DELETE FROM "public"."sys_client" WHERE "id" = 2; +-- rollback ALTER TABLE "public"."sys_client" DROP COLUMN "refresh_token_timeout"; +-- rollback ALTER TABLE "public"."sys_client" DROP COLUMN "is_enable_refresh_token";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql` around lines 1 - 21, Fix formatting and make the changeset idempotent and rollback-safe: normalize indentation for the ADD COLUMN lines, qualify the INSERT with the same schema ("public"."sys_client"), and replace the hard-coded INSERT that may fail with an idempotent upsert (e.g., use INSERT ... ON CONFLICT (id) DO NOTHING or INSERT ... WHERE NOT EXISTS (SELECT 1 FROM "public"."sys_client" WHERE id = 2) or better upsert by unique client_id) so re-running won’t error; finally add an explicit rollback block that drops the two columns ("is_enable_refresh_token", "refresh_token_timeout") and removes the seeded row (by id or client_id) to ensure clean rollback of both DDL and DML.continew-server/src/main/resources/db/changelog/db.changelog-master.yaml (1)
14-15:includeAll按文件名字典序加载,建议规范 changeset 文件命名。Liquibase
includeAll按字典序加载目录下的文件;当前使用的v4.2.0_1_change.sql若后续出现v4.2.0_10_xxx.sql,会被排在v4.2.0_2_xxx.sql之前,可能导致与作者预期不一致的执行顺序。建议后续新增文件时用零填充(如v4.2.0_001_change.sql),或在includeAll中显式配置resourceComparator以规避排序陷阱。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-server/src/main/resources/db/changelog/db.changelog-master.yaml` around lines 14 - 15, 当前使用的 includeAll (path: db/changelog/mysql/V4.2.0) 会按字典序加载文件,导致类似 v4.2.0_10_xxx.sql 排序在 v4.2.0_2_xxx.sql 之前,建议修复:要么统一重命名现有和后续 changeset 文件(例如将 v4.2.0_1_change.sql 改为 v4.2.0_001_change.sql,使用零填充以保证数值顺序),要么在 includeAll 配置中指定 resourceComparator 来控制加载顺序(例如按数字或自定义比较器);定位关键词 includeAll、path db/changelog/mysql/V4.2.0、v4.2.0_1_change.sql 和 resourceComparator 来修改相应配置或重命名文件。continew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sql (1)
6-15: 建议让 INSERT 幂等并补充 rollback 声明。
- Line 11-15 直接插入
id = 2,若目标环境已存在该主键(例如此前已手工写入或从其它环境同步),changeset 会因唯一键冲突失败,且上方 ALTER TABLE 一并回滚,产生“部分执行但全部失败”的困扰。建议改为INSERT ... ON DUPLICATE KEY UPDATE id = id或INSERT IGNORE,使之幂等。- Liquibase 建议为每个 changeset 显式提供
-- rollback指令,目前仅依赖自动推断,组合 DDL + DML 时不稳妥。- Line 8 缩进与 line 7 不一致,建议统一格式。
建议的 diff
ALTER TABLE `sys_client` ADD COLUMN `is_enable_refresh_token` bit NOT NULL DEFAULT FALSE COMMENT '是否启用refresh token (true: 启用; false: 禁用)' AFTER `timeout`, -ADD COLUMN `refresh_token_timeout` bigint NULL DEFAULT 2592000 COMMENT 'Refresh token有效期(单位:秒; 值必须大于0,否则取token的有效时长)' AFTER `is_enable_refresh_token`; + ADD COLUMN `refresh_token_timeout` bigint NULL DEFAULT 2592000 COMMENT 'Refresh token有效期(单位:秒; 值必须大于0,否则取token的有效时长)' AFTER `is_enable_refresh_token`; -- 初始化客户端数据 -INSERT INTO `sys_client` +INSERT IGNORE INTO `sys_client` (`id`, `client_id`, `client_type`, `auth_type`, `active_timeout`, `timeout`, `status`, `create_user`, `create_time`, `is_enable_refresh_token`, `refresh_token_timeout`) VALUES (2, 'ef51c9a3e9046c4f2ea45142c8a8344b', 'XCX', '["ACCOUNT", "EMAIL", "PHONE", "SOCIAL"]', 1800, 86400, 1, 1, NOW(), b'1', 2592000); + +-- rollback DELETE FROM `sys_client` WHERE `id` = 2; +-- rollback ALTER TABLE `sys_client` DROP COLUMN `refresh_token_timeout`, DROP COLUMN `is_enable_refresh_token`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sql` around lines 6 - 15, 当前 changeset 对 sys_client 的 INSERT 使用固定主键 id=2 会在目标库已存在该主键时报错并回滚,同时未提供显式 rollback。将 INSERT INTO `sys_client` ... VALUES(...) 修改为幂等写法(例如使用 INSERT ... ON DUPLICATE KEY UPDATE id = id 或 INSERT IGNORE)以避免唯一键冲突,并在同一 changeset 中补充明确的 rollback 描述(rollback 应撤销 INSERT 的行并回退对表 `sys_client` 的列改动 `is_enable_refresh_token` 和 `refresh_token_timeout`,例如删除列与删除该行),另外统一调整 ALTER/ADD 语句的缩进(涉及 `ALTER TABLE \`sys_client\`` 和新增列 `is_enable_refresh_token`/`refresh_token_timeout`)以保持格式一致。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java`:
- Around line 75-80: The refreshToken controller method currently accepts a
plain String parameter (public LoginResp refreshToken(String refreshToken)) with
no Spring binding annotation so POST JSON bodies will yield null; update the
method to explicitly bind the input—either change the signature to accept a DTO
with `@RequestBody` (e.g., RefreshTokenReq req) and extract req.getRefreshToken(),
or annotate the existing parameter with `@RequestParam` and validation (e.g.,
`@RequestParam` `@NotBlank` String refreshToken); also add a Swagger `@Parameter` or
Operation note for the chosen approach and update AuthController.refreshToken
and any related DTO (e.g., RefreshTokenReq) accordingly.
In `@continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java`:
- Around line 84-109: Rename the method bulderLoginResp to buildLoginResp and
update its two call sites in AuthServiceImpl and AbstractLoginHandler; inside
buildLoginResp replace the boolean check client.getIsEnableRefreshToken() with
Boolean.TRUE.equals(client.getIsEnableRefreshToken()), guard the nullable
refresh token timeout by reading Long rtTimeout =
client.getRefreshTokenTimeout() and compute refreshExpiresIn = (rtTimeout !=
null && rtTimeout > 0L) ? rtTimeout : client.getTimeout(), and reduce repeated
calls by caching StpUtil.getTokenValue(), StpUtil.getTokenTimeout(), and
tenantId (TenantContextHolder.isTenantEnabled() ?
TenantContextHolder.getTenantId() : null) into local variables before building
the response. Ensure loginParameter.setToken(...) still runs when saving the
refresh token.
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java`:
- Around line 26-42: Add Lombok's `@EqualsAndHashCode`(callSuper = true) to the
SingleTokenLoginResp class so its generated equals/hashCode include parent
LoginResp fields, and update the expiresIn field's `@Schema` example from "8600"
to "86400" to reflect one day in seconds; target the class declaration
annotation list (SingleTokenLoginResp) and the expiresIn field's `@Schema`
attribute.
In
`@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java`:
- Around line 134-151: Refresh flow is unsafe: parseToken->cast->use stale
UserContext->kickout old token before creating new one and delete refresh token
unconditionally; fix by (1) safely parse and type-check SaTempUtil.parseToken
result and loginParameter (SaLoginParameter) and its extra UserContext with
instanceof checks and throw a BusinessException with distinct error codes for
expired vs invalid tokens; (2) reload fresh user data from userService (e.g.,
fetch by user id from parsed loginParameter) and rebuild UserContext before
setting UserContextHolder; (3) validate client via
clientService.getByClientId(userContext.getClientId()) for
null/disabled/refresh-disabled like login(...) does; (4) create the new session
/ LoginResp via LoginHandler.bulderLoginResp only after successful validation,
then atomically kick old session with
StpUtil.kickoutByTokenValue(loginParameter.getToken()) and remove old refresh
token with SaTempUtil.deleteToken(refreshToken) in the success/finally path so
failures don’t leave user logged out without a new token; also consider
concurrent-refresh protection by ensuring deleteToken/kickout happens after new
token creation or using a CAS/lock around refreshToken handling.
In
`@continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java`:
- Around line 79-90: Update the validation message on the isEnableRefreshToken
field to accurately reflect what is being validated by changing the `@NotNull`
message to something like "是否启用 Refresh Token 不能为空", and add a conditional
non-null validation for refreshTokenTimeout using the same pattern as
replacedRange/overflowLogoutMode (apply `@SpelNotNull` on refreshTokenTimeout with
a condition checking isEnableRefreshToken == true) so that when
isEnableRefreshToken is true the refreshTokenTimeout cannot be null.
In
`@continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java`:
- Around line 86-98: The ExcelProperty order is duplicated (order = 7) for
timeout, isEnableRefreshToken and refreshTokenTimeout and the Boolean field name
is problematic for JSON; rename the Boolean field isEnableRefreshToken to
enableRefreshToken (update any usages) to avoid Lombok/Jackson isXxx vs xxx
mismatch, remove the "is" prefix from the property and its annotations, and
reassign unique incremental ExcelProperty order values for enableRefreshToken
and refreshTokenTimeout (and shift any subsequent fields' order accordingly) to
eliminate the conflict; ensure any code referencing
getIsEnableRefreshToken/setIsEnableRefreshToken is updated to the new
getter/setter names for enableRefreshToken.
---
Outside diff comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java`:
- Around line 21-45: LoginResp's OpenAPI schema lacks the subclass token fields
because AuthController.login()/refreshToken() return the LoginResp base type;
update the LoginResp class to declare its polymorphic subtypes so Swagger sees
the actual response shapes — add a `@Schema` on LoginResp referencing oneOf =
{SingleTokenLoginResp.class, DoubleTokenLoginResp.class} or use
`@Schema`(discriminatorProperty = "...") with discriminator values on the
subclasses (SingleTokenLoginResp, DoubleTokenLoginResp) so the generated docs
include token/accessToken/refreshToken fields for the endpoints.
---
Duplicate comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java`:
- Around line 26-53: DoubleTokenLoginResp (like SingleTokenLoginResp) currently
uses Lombok's `@Data` which generates equals/hashCode without calling the
superclass, causing LoginResp fields (e.g., tenantId) to be ignored; fix by
adding Lombok's `@EqualsAndHashCode`(callSuper = true) to the DoubleTokenLoginResp
class declaration so equals/hashCode include LoginResp fields (ensure the
import/annotation is added alongside `@Data/`@SuperBuilder).
---
Nitpick comments:
In `@continew-server/src/main/resources/db/changelog/db.changelog-master.yaml`:
- Around line 14-15: 当前使用的 includeAll (path: db/changelog/mysql/V4.2.0)
会按字典序加载文件,导致类似 v4.2.0_10_xxx.sql 排序在 v4.2.0_2_xxx.sql 之前,建议修复:要么统一重命名现有和后续
changeset 文件(例如将 v4.2.0_1_change.sql 改为 v4.2.0_001_change.sql,使用零填充以保证数值顺序),要么在
includeAll 配置中指定 resourceComparator 来控制加载顺序(例如按数字或自定义比较器);定位关键词 includeAll、path
db/changelog/mysql/V4.2.0、v4.2.0_1_change.sql 和 resourceComparator
来修改相应配置或重命名文件。
In
`@continew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sql`:
- Around line 6-15: 当前 changeset 对 sys_client 的 INSERT 使用固定主键 id=2
会在目标库已存在该主键时报错并回滚,同时未提供显式 rollback。将 INSERT INTO `sys_client` ... VALUES(...)
修改为幂等写法(例如使用 INSERT ... ON DUPLICATE KEY UPDATE id = id 或 INSERT
IGNORE)以避免唯一键冲突,并在同一 changeset 中补充明确的 rollback 描述(rollback 应撤销 INSERT 的行并回退对表
`sys_client` 的列改动 `is_enable_refresh_token` 和
`refresh_token_timeout`,例如删除列与删除该行),另外统一调整 ALTER/ADD 语句的缩进(涉及 `ALTER TABLE
\`sys_client\`` 和新增列 `is_enable_refresh_token`/`refresh_token_timeout`)以保持格式一致。
In
`@continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql`:
- Around line 1-21: Fix formatting and make the changeset idempotent and
rollback-safe: normalize indentation for the ADD COLUMN lines, qualify the
INSERT with the same schema ("public"."sys_client"), and replace the hard-coded
INSERT that may fail with an idempotent upsert (e.g., use INSERT ... ON CONFLICT
(id) DO NOTHING or INSERT ... WHERE NOT EXISTS (SELECT 1 FROM
"public"."sys_client" WHERE id = 2) or better upsert by unique client_id) so
re-running won’t error; finally add an explicit rollback block that drops the
two columns ("is_enable_refresh_token", "refresh_token_timeout") and removes the
seeded row (by id or client_id) to ensure clean rollback of both DDL and DML.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a51cfe35-7739-4d24-97f1-16d91a40b1ce
📒 Files selected for processing (15)
continew-server/src/main/resources/db/changelog/db.changelog-master.yamlcontinew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sqlcontinew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sqlcontinew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/LoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/constant/AuthConstants.javacontinew-system/src/main/java/top/continew/admin/auth/controller/AuthController.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/service/AuthService.javacontinew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.javacontinew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.javacontinew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.javacontinew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java (1)
41-41:⚠️ Potential issue | 🟡 Minor
expiresIn的@Schema示例值疑似有误
example = "8600"看起来应为"86400"(一天 = 86400 秒),与常见的 token 有效期惯例一致,避免在 OpenAPI 文档中误导调用方。🔧 建议修正
- `@Schema`(description = "有效时长(秒)", example = "8600") + `@Schema`(description = "有效时长(秒)", example = "86400") private Long expiresIn;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java` at line 41, Update the `@Schema` example for the expiresIn field in SingleTokenLoginResp to the correct value: change the example from "8600" to "86400" (one day in seconds) so the OpenAPI documentation for the expiresIn property reflects the intended token lifetime; locate the expiresIn field and its `@Schema` annotation in the SingleTokenLoginResp class and replace the example value accordingly.continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java (1)
134-151:⚠️ Potential issue | 🔴 Critical此前指出的健壮性/安全隐患仍未解决
对照上一轮评审,这段代码没有实质改动,下列问题依然存在(按严重度排序):
client没有非空/禁用校验就直接用(Line 144 → Line 147)。如果userContext.getClientId()对应的客户端已被删除或禁用,clientService.getByClientId(...)会返回null,随后LoginHandler.buildLoginResp中client.getIsEnableRefreshToken()会直接 NPE;被禁用的客户端理应拒绝刷新而不是继续签发新令牌。建议与login(...)保持同样的三条校验(非空 / 未禁用 / 仍启用 refresh token)。- 先踢旧 Token、后建新会话(Line 146 先于 Line 147)。一旦
buildLoginResp抛异常(DB 异常、Sa-Token 异常等),旧访问令牌已失效但新令牌未返回,用户被"静默登出"。同时 Line 149 的SaTempUtil.deleteToken(refreshToken)也要在成功路径末尾执行,异常路径下 Refresh Token 还能被再次使用,形成不一致窗口。建议顺序调整为:建新会话 → 成功后再 kickout 旧 access token + deleteToken 旧 refresh token。- 使用登录时快照的
UserContext(Line 142-143)。权限、角色、密码过期天数、启用状态等在登录后可能已变化,refresh 却无限复用旧快照,相当于权限回收被绕过。应基于userContext.getId()重新加载并重建UserContext。- 裸
ClassCastException风险(Line 141-142)。parseToken反序列化结果若因版本升级/字段改动不匹配,两个强转都会抛出非业务异常。建议instanceof判断后抛BusinessException。- 过期与无效无法区分(Line 136-139)。
SaTempUtil.parseToken()对两种情况都返回null,当前统一报无效的Refresh Token,前端无法区分是否需要引导用户重新登录 vs 直接提示过期。- 并发刷新可签发两份新会话。同一个 Refresh Token 被并发调用两次时两个请求都能通过
parseToken,建议把deleteToken前置到buildLoginResp之前(或借助分布式锁 / CAS),确保并发中只有一个能继续。这些全部是之前评审已指出但未处理的项,留作跟踪。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java` around lines 134 - 151, Refresh-token handling must be hardened: first validate parseToken return and types (use instanceof on the result to ensure it's a SaLoginParameter and that its extra is a UserContext, otherwise throw BusinessException), then reload current UserContext from persistence using userContext.getId() instead of reusing the login snapshot; next fetch the client via clientService.getByClientId(...) and enforce the same three checks as login (client non-null, not disabled, refresh-token feature enabled) and throw BusinessException if any fail; reorder operations so you construct the new session/response via LoginHandler.buildLoginResp(...) before calling StpUtil.kickoutByTokenValue(...) or SaTempUtil.deleteToken(...), and only delete the refresh token after buildLoginResp succeeds; finally, add concurrency protection around deleteToken (e.g., distributed lock or CAS) to avoid double-refresh races and, if possible, differentiate null/expired tokens from invalid ones by invoking Sa-Token validation APIs or explicit checks so you can return distinct BusinessException messages.
🧹 Nitpick comments (2)
continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java (1)
24-30: 建议与父类LoginResp保持元数据一致。当前类头信息较为单薄,与父类
LoginResp(含@author、@since、类级@Schema、serialVersionUID)和兄弟类SingleTokenLoginResp不一致,建议补齐:
- 类级
@Schema(description = "双令牌登录响应参数"):父类上的@Schema不会被 springdoc 继承到子类,子类在 OpenAPI 文档中会缺失描述。- Javadoc 补充
@author/@since,与项目其他 DTO 风格保持一致。- 由于父类实现了
Serializable,建议子类显式声明serialVersionUID,避免 JVM 版本差异导致的隐式计算不一致。♻️ 建议的调整
/** - * 双token模式 + * 双令牌登录响应参数 + * + * `@author` luoqiz + * `@since` 2026/4/24 */ `@Data` `@SuperBuilder` `@EqualsAndHashCode`(callSuper = true) +@Schema(description = "双令牌登录响应参数") public class DoubleTokenLoginResp extends LoginResp { + `@Serial` + private static final long serialVersionUID = 1L; + /** * 访问令牌 */相应需补充 import:
import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.SuperBuilder; + +import java.io.Serial;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java` around lines 24 - 30, Add the same metadata to DoubleTokenLoginResp as in LoginResp/SingleTokenLoginResp: add a class-level `@Schema`(description = "双令牌登录响应参数"), include Javadoc tags `@author` and `@since` above the class, and declare a private static final long serialVersionUID with a stable value; ensure imports for io.swagger.v3.oas.annotations.media.Schema and java.io.Serializable are present and keep the class annotation set (`@Data`, `@SuperBuilder`, `@EqualsAndHashCode`(callSuper = true)) intact so OpenAPI docs and serialization behavior match the parent and sibling DTOs.continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java (1)
76-81: 接口绑定已到位,可补充参数文档
@RequestParam("refreshToken")@NotBlank`` 已经解决了之前评论指出的绑定缺失问题,功能上没有问题。可选优化:
@Operation的 summary/description 都是刷新token,与其它接口(如登录)的风格一致性一般,可以改为刷新 Token/使用 Refresh Token 换取新的访问令牌,方便 Swagger 文档阅读。- 缺少
@Parameter描述,建议补一个便于前端联调。- 如果未来希望前端以 JSON Body 方式传参(移动端/网关场景常见),可以考虑改为
@RequestBody RefreshTokenReq。♻️ 可选优化
- `@SaIgnore` - `@Operation`(summary = "刷新token", description = "刷新token") - `@PostMapping`("/refreshToken") - public LoginResp refreshToken(`@RequestParam`("refreshToken") `@NotBlank` String refreshToken) { - return authService.refreshToken(refreshToken); - } + `@SaIgnore` + `@Operation`(summary = "刷新 Token", description = "使用 Refresh Token 换取新的访问令牌") + `@Parameter`(name = "refreshToken", description = "刷新令牌", required = true, in = ParameterIn.QUERY) + `@PostMapping`("/refreshToken") + public LoginResp refreshToken(`@RequestParam`("refreshToken") `@NotBlank` String refreshToken) { + return authService.refreshToken(refreshToken); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java` around lines 76 - 81, Update the AuthController.refreshToken endpoint metadata to improve API docs: change `@Operation` summary/description to clearer text like "刷新 Token" and "使用 Refresh Token 换取新的访问令牌", and add a `@Parameter` annotation describing the refreshToken parameter for Swagger; keep the existing `@RequestParam`("refreshToken") `@NotBlank` String refreshToken signature (or optionally convert to `@RequestBody` RefreshTokenReq in future if switching to JSON body).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@continew-server/src/main/resources/config/application-dev.yml`:
- Around line 15-17: Revert the personal-host and plaintext credentials in
application-dev.yml by replacing the hardcoded DB and Redis defaults
(DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PWD and Redis host/port/password/database)
with local-safe placeholders (e.g., 127.0.0.1 and non-sensitive defaults or
empty values) and restore the prior team dev defaults; ensure consumers rely on
environment variables or a non-committed override file (e.g.,
application-local.yml) for personal credentials, add that override filename to
.gitignore if missing, and if the exposed DB/Redis credentials are still active
rotate those credentials immediately.
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java`:
- Around line 35-42: The `@Schema` example for refreshToken in class
DoubleTokenLoginResp currently duplicates accessToken; change the refreshToken
example to a distinct placeholder value (e.g.,
"refresh-token-placeholder-<unique>") so Swagger/OpenAPI shows different
examples for accessToken and refreshToken and to avoid static-scan confusion;
update the `@Schema` annotation on the refreshToken field (symbol: refreshToken)
accordingly while leaving accessToken's example unchanged.
---
Duplicate comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java`:
- Line 41: Update the `@Schema` example for the expiresIn field in
SingleTokenLoginResp to the correct value: change the example from "8600" to
"86400" (one day in seconds) so the OpenAPI documentation for the expiresIn
property reflects the intended token lifetime; locate the expiresIn field and
its `@Schema` annotation in the SingleTokenLoginResp class and replace the example
value accordingly.
In
`@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java`:
- Around line 134-151: Refresh-token handling must be hardened: first validate
parseToken return and types (use instanceof on the result to ensure it's a
SaLoginParameter and that its extra is a UserContext, otherwise throw
BusinessException), then reload current UserContext from persistence using
userContext.getId() instead of reusing the login snapshot; next fetch the client
via clientService.getByClientId(...) and enforce the same three checks as login
(client non-null, not disabled, refresh-token feature enabled) and throw
BusinessException if any fail; reorder operations so you construct the new
session/response via LoginHandler.buildLoginResp(...) before calling
StpUtil.kickoutByTokenValue(...) or SaTempUtil.deleteToken(...), and only delete
the refresh token after buildLoginResp succeeds; finally, add concurrency
protection around deleteToken (e.g., distributed lock or CAS) to avoid
double-refresh races and, if possible, differentiate null/expired tokens from
invalid ones by invoking Sa-Token validation APIs or explicit checks so you can
return distinct BusinessException messages.
---
Nitpick comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java`:
- Around line 76-81: Update the AuthController.refreshToken endpoint metadata to
improve API docs: change `@Operation` summary/description to clearer text like "刷新
Token" and "使用 Refresh Token 换取新的访问令牌", and add a `@Parameter` annotation
describing the refreshToken parameter for Swagger; keep the existing
`@RequestParam`("refreshToken") `@NotBlank` String refreshToken signature (or
optionally convert to `@RequestBody` RefreshTokenReq in future if switching to
JSON body).
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java`:
- Around line 24-30: Add the same metadata to DoubleTokenLoginResp as in
LoginResp/SingleTokenLoginResp: add a class-level `@Schema`(description =
"双令牌登录响应参数"), include Javadoc tags `@author` and `@since` above the class, and
declare a private static final long serialVersionUID with a stable value; ensure
imports for io.swagger.v3.oas.annotations.media.Schema and java.io.Serializable
are present and keep the class annotation set (`@Data`, `@SuperBuilder`,
`@EqualsAndHashCode`(callSuper = true)) intact so OpenAPI docs and serialization
behavior match the parent and sibling DTOs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9e1dea10-b894-493d-87ad-f72e6610a412
📒 Files selected for processing (7)
continew-server/src/main/resources/config/application-dev.ymlcontinew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/LoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/controller/AuthController.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java
🚧 Files skipped from review as they are similar to previous changes (2)
- continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java
- continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java
c9285bd to
f8e111f
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java (1)
134-158:⚠️ Potential issue | 🔴 Critical刷新流程仍存在多处严重健壮性缺陷,且
finally写法新增了「刷新失败即彻底登出」的事故路径1.
finally块的清理语义反了(新增的严重风险)
将kickoutByTokenValue(oldToken)+SaTempUtil.deleteToken(refreshToken)放入finally,意味着只要bulderLoginResp(...)中途抛出异常(例如下一条提到的client == null导致client.getIsEnableRefreshToken()NPE、DB 抖动、StpUtil.login内部异常等),就会出现两种糟糕的后果:
- 抛异常发生在
StpUtil.login之前:用户旧 Access Token 被踢出 + Refresh Token 被删除,但没有拿到新会话,彻底登出且无法自救。- 抛异常发生在
StpUtil.login之后、return之前:后台已经创建了新会话,但客户端没收到响应;旧会话被销毁,Refresh Token 也被销毁,服务器上残留一个客户端永远不会使用的孤儿会话。正确的语义是「只有刷新成功后才清理旧凭据」。把两行清理移出
finally,放在return之前(或包装到成功路径末尾的原子步骤中)。2.
client未判空 / 未校验状态(过去评审已提出,仍未处理)
clientService.getByClientId(userContext.getClientId())可能返回null;也没有校验DisEnableStatusEnum.DISABLE与isEnableRefreshToken。login(...)方法在 Line 80-81 已有成熟校验,刷新路径应对齐,否则「禁用客户端」或「已关闭刷新令牌」的情况仍可刷新成功。3. 类型强转未做
instanceof防御(过去评审已提出,仍未处理)
(SaLoginParameter) refreshTokenInfo、(UserContext) loginParameter.getExtra(AuthConstants.LOGIN_USER)在序列化结构变更 / 不同版本 Token 混用时会抛裸ClassCastException。4. UserContext 陈旧导致权限回收绕过(过去评审已提出,仍未处理)
直接复用登录时快照的UserContext(权限、角色、启用状态、租户等)。用户被禁用、角色被调整后,仍可凭旧 Refresh Token 无限刷出含原权限的 Access Token。应通过userContext.getId()重新加载用户并重建UserContext(与AbstractLoginHandler.authenticate一致的路径),同时校验用户启用状态。5. 过期 vs 无效无法区分(过去评审已提出,仍未处理)
SaTempUtil.parseToken对过期与非法 Token 均返回null,统一抛BusinessException("无效的Refresh Token")会让前端失去"过期请重新登录"与"非法令牌"场景的差异化处理。建议使用专用错误码。🛠️ 建议的修复方向
`@Override` public LoginResp refreshToken(String refreshToken) { Object refreshTokenInfo = SaTempUtil.parseToken(refreshToken); - if (refreshTokenInfo == null) { - throw new BusinessException("无效的Refresh Token"); + if (!(refreshTokenInfo instanceof SaLoginParameter loginParameter)) { + // 过期或无效,建议使用独立错误码以便前端区分 + throw new BusinessException("Refresh Token 已过期或无效"); } - String oldToken = null; - try { - // 获取登录时设置的参数 - SaLoginParameter loginParameter = (SaLoginParameter) refreshTokenInfo; - UserContext userContext = (UserContext) loginParameter.getExtra(AuthConstants.LOGIN_USER); - UserContextHolder.setContext(userContext); - ClientResp client = clientService.getByClientId(userContext.getClientId()); - oldToken = loginParameter.getToken(); - LoginResp loginResp = LoginHandler.buildLoginResp(loginParameter, userContext, client); - return loginResp; - } finally { - if (oldToken != null) { - // 删除原先的 Token - StpUtil.kickoutByTokenValue(oldToken); - } - // 删除原先的 Refresh token - SaTempUtil.deleteToken(refreshToken); - } + Object loginUser = loginParameter.getExtra(AuthConstants.LOGIN_USER); + if (!(loginUser instanceof UserContext staleContext)) { + throw new BusinessException("Refresh Token 数据结构异常"); + } + // 校验客户端(对齐 login(...) 的校验) + ClientResp client = clientService.getByClientId(staleContext.getClientId()); + ValidationUtils.throwIfNull(client, "客户端不存在"); + ValidationUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(client.getStatus()), "客户端已禁用"); + ValidationUtils.throwIf(!Boolean.TRUE.equals(client.getIsEnableRefreshToken()), "该客户端未开启刷新令牌"); + // TODO: 根据 staleContext.getId() 重新从库中加载用户并重建 UserContext, + // 校验启用状态、重新拉取权限/角色,避免使用陈旧快照导致权限回收被绕过。 + UserContext userContext = reloadUserContext(staleContext, client); + UserContextHolder.setContext(userContext); + String oldToken = loginParameter.getToken(); + // 仅在成功构建新会话后,再清理旧凭据(非 finally) + LoginResp loginResp = LoginHandler.buildLoginResp(loginParameter, userContext, client); + if (oldToken != null) { + StpUtil.kickoutByTokenValue(oldToken); + } + SaTempUtil.deleteToken(refreshToken); + return loginResp; }若有并发刷新担忧(同一 Refresh Token 被并发调用两次),可在
parseToken成功后立即deleteToken,并通过删除动作的结果/缓存原子操作进行「最多一次消费」语义的判定,再继续后续构建。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java` around lines 134 - 158, The refreshToken method currently parses the refresh token and performs cleanup in a finally block causing logout on any intermediate failure; fix by: validate parse result with instanceof checks for SaLoginParameter and its extra as UserContext, reload fresh UserContext from user id (not reuse the serialized snapshot) and validate user enabled status, call clientService.getByClientId(userContext.getClientId()) and assert client != null and client is enabled and client.getIsEnableRefreshToken() before proceeding, perform the build/login steps (LoginHandler.buildLoginResp / StpUtil.login) and only after successful completion remove the old token via StpUtil.kickoutByTokenValue(oldToken) and delete the refresh token via SaTempUtil.deleteToken(refreshToken) (move those out of finally into the success path), and change error handling from a single "无效的Refresh Token" to distinct error codes/messages for expired vs invalid tokens (use parseToken result or SaTempUtil semantics) and consider deleting the temp token immediately after successful parse for at-most-once consumption to guard against concurrent refresh calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java`:
- Around line 134-158: The refreshToken method currently parses the refresh
token and performs cleanup in a finally block causing logout on any intermediate
failure; fix by: validate parse result with instanceof checks for
SaLoginParameter and its extra as UserContext, reload fresh UserContext from
user id (not reuse the serialized snapshot) and validate user enabled status,
call clientService.getByClientId(userContext.getClientId()) and assert client !=
null and client is enabled and client.getIsEnableRefreshToken() before
proceeding, perform the build/login steps (LoginHandler.buildLoginResp /
StpUtil.login) and only after successful completion remove the old token via
StpUtil.kickoutByTokenValue(oldToken) and delete the refresh token via
SaTempUtil.deleteToken(refreshToken) (move those out of finally into the success
path), and change error handling from a single "无效的Refresh Token" to distinct
error codes/messages for expired vs invalid tokens (use parseToken result or
SaTempUtil semantics) and consider deleting the temp token immediately after
successful parse for at-most-once consumption to guard against concurrent
refresh calls.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ff436e0b-e22c-4441-8e71-76b7c056c312
📒 Files selected for processing (2)
continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.javacontinew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java (1)
76-84: Javadoc 不完整 & 静态工具方法放在接口中欠妥
@return标签内容为空,Checkstyle / IDE 会提示,请补充说明(例如 "登录响应参数,启用刷新令牌时返回DoubleTokenLoginResp,否则返回SingleTokenLoginResp")。buildLoginResp是一个与LoginHandler契约无直接关系的工具方法,目前以static形式挂在接口上,调用方AbstractLoginHandler/AuthServiceImpl都需通过LoginHandler.buildLoginResp(...)调用,语义上更像"登录响应构造器"。建议抽到独立的工具类(如LoginRespBuilder或放入AbstractLoginHandler作为protected static),让LoginHandler接口保持单一职责。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java` around lines 76 - 84, The Javadoc for buildLoginResp in the LoginHandler interface is incomplete (missing `@return`) and the static utility method doesn't belong on the interface; update the Javadoc to describe the returned LoginResp (e.g., "登录响应参数,启用刷新令牌时返回 DoubleTokenLoginResp,否则返回 SingleTokenLoginResp") and move buildLoginResp out of the LoginHandler interface into a dedicated utility/constructor location (create a LoginRespBuilder class or make it a protected static method on AbstractLoginHandler) then update callers (AbstractLoginHandler and AuthServiceImpl) to call the new LoginRespBuilder.build(...) or AbstractLoginHandler.buildLoginResp(...) accordingly.continew-server/src/main/resources/db/changelog/db.changelog-master.yaml (1)
14-15:includeAll目录加载顺序依赖文件名排序,建议固化命名约定Liquibase 的
includeAll按文件名自然顺序加载目录下所有 changelog 文件。当前v4.2.0_1_change.sql命名能正确排序,但后续往V4.2.0目录中追加文件时(如v4.2.0_10_xxx.sql),字典序可能与预期执行顺序不一致。建议:
- 采用固定位数的序号(如
v4.2.0_001_change.sql),或- 显式指定
relativeToChangelogFile: true并在文档中明确命名规范,避免后续协作者破坏执行顺序。此外建议同步在 PostgreSQL 代码块中将
includeAll取消注释(目前仍注释,可能导致 PG 环境下刷新令牌列缺失)——或至少在 README/升级文档中说明需手动启用。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-server/src/main/resources/db/changelog/db.changelog-master.yaml` around lines 14 - 15, 当前 db.changelog-master.yaml 中使用 includeAll: path: db/changelog/mysql/V4.2.0 依赖文件名自然排序,可能导致像 v4.2.0_10_xxx.sql 排序异常;请在文档和模板中固化命名规则(例如 改为 v4.2.0_001_change.sql 固定位数序号)或改为显式使用带序号前缀的命名约定,并在仓库 README/升级文档中写明;同时检查 PostgreSQL 部分(当前为注释状态的 includeAll 块),将其取消注释以同步包含相应 changelog 或在文档中明确说明需要手动启用 includeAll,确保 refresh_token 列在 PG 环境下不会缺失。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@continew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sql`:
- Around line 6-8: Update the new column definition on table sys_client to match
existing boolean-style columns: change the type for is_enable_refresh_token from
bit to bit(1) and change its default from DEFAULT FALSE to DEFAULT b'0' so it
matches is_concurrent's bit(1) and DEFAULT b'1' style; adjust the ALTER TABLE
statement that adds is_enable_refresh_token accordingly and keep
refresh_token_timeout as-is.
In `@continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java`:
- Around line 90-93: The refresh-token flow currently serializes loginParameter
(which contains a cached UserContext) via SaTempUtil.saveToken and then in
AuthServiceImpl#refreshToken restores UserContext from
loginParameter.getExtra(AuthConstants.LOGIN_USER), allowing stale or disabled
user state to persist; change the refresh logic to ignore the cached UserContext
from loginParameter and instead reload the current UserContext from the database
by user id (or at minimum verify the user's enabled/tenant/soft-delete status)
before issuing a new access token, updating references around loginParameter,
SaTempUtil.saveToken, and AuthServiceImpl#refreshToken to use the freshly
loaded/validated UserContext.
---
Nitpick comments:
In `@continew-server/src/main/resources/db/changelog/db.changelog-master.yaml`:
- Around line 14-15: 当前 db.changelog-master.yaml 中使用 includeAll: path:
db/changelog/mysql/V4.2.0 依赖文件名自然排序,可能导致像 v4.2.0_10_xxx.sql
排序异常;请在文档和模板中固化命名规则(例如 改为 v4.2.0_001_change.sql 固定位数序号)或改为显式使用带序号前缀的命名约定,并在仓库
README/升级文档中写明;同时检查 PostgreSQL 部分(当前为注释状态的 includeAll 块),将其取消注释以同步包含相应 changelog
或在文档中明确说明需要手动启用 includeAll,确保 refresh_token 列在 PG 环境下不会缺失。
In `@continew-system/src/main/java/top/continew/admin/auth/LoginHandler.java`:
- Around line 76-84: The Javadoc for buildLoginResp in the LoginHandler
interface is incomplete (missing `@return`) and the static utility method doesn't
belong on the interface; update the Javadoc to describe the returned LoginResp
(e.g., "登录响应参数,启用刷新令牌时返回 DoubleTokenLoginResp,否则返回 SingleTokenLoginResp") and
move buildLoginResp out of the LoginHandler interface into a dedicated
utility/constructor location (create a LoginRespBuilder class or make it a
protected static method on AbstractLoginHandler) then update callers
(AbstractLoginHandler and AuthServiceImpl) to call the new
LoginRespBuilder.build(...) or AbstractLoginHandler.buildLoginResp(...)
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9b752908-cfb6-4e32-b097-765c20120fd1
📒 Files selected for processing (15)
continew-server/src/main/resources/db/changelog/db.changelog-master.yamlcontinew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sqlcontinew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sqlcontinew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/LoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/constant/AuthConstants.javacontinew-system/src/main/java/top/continew/admin/auth/controller/AuthController.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/service/AuthService.javacontinew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.javacontinew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.javacontinew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.javacontinew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java
✅ Files skipped from review due to trivial changes (2)
- continew-system/src/main/java/top/continew/admin/auth/constant/AuthConstants.java
- continew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.java
🚧 Files skipped from review as they are similar to previous changes (8)
- continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java
- continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java
- continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java
- continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java
- continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java
- continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java
- continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java
- continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java
f8e111f to
3b0f6e7
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java (1)
32-45:⚠️ Potential issue | 🟡 Minor注意:
LoginResp字段重构为继承体系,需确认前端反序列化兼容性。
LoginResp基类现仅保留tenantId字段,token/expiresIn已迁移至子类SingleTokenLoginResp(单 token 模式),而DoubleTokenLoginResp则采用accessToken/refreshToken(双 token 模式)。运行时根据client.getIsEnableRefreshToken配置动态返回相应子类,这对前端反序列化可能造成影响:
- 若前端强类型化为
LoginResp并直接访问token/expiresIn字段,会因字段不存在而报错。- 若前端采用多态反序列化或使用
SingleTokenLoginResp/DoubleTokenLoginResp子类型,则不受影响。请确认:(1) 前端反序列化策略已适配此继承体系(如支持多态或直接使用子类);(2) CHANGELOG 已补充此变更说明;(3) API 文档已明确标注响应类型为子类而非基类。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java` around lines 32 - 45, LoginResp was refactored into a base class and now only contains tenantId while token/expiresIn moved to SingleTokenLoginResp and accessToken/refreshToken to DoubleTokenLoginResp; verify and update consumers and docs: ensure frontend deserialization handles polymorphism or uses the correct subtype (SingleTokenLoginResp/DoubleTokenLoginResp) depending on client.getIsEnableRefreshToken, update API docs to state responses are subtype-specific rather than LoginResp, and add a CHANGELOG entry describing this breaking change so integrators know to switch from accessing token/expiresIn on LoginResp to the appropriate subclass fields.
♻️ Duplicate comments (1)
continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java (1)
134-154:⚠️ Potential issue | 🔴 Critical刷新令牌流程仍有多项健壮性/安全性问题未完成修复。
client为空 / 已禁用未校验:Line 144clientService.getByClientId(userContext.getClientId())后直接传入buildLoginResp,若客户端被删除 / 禁用 / 关闭了 refresh token,仍会签发新令牌,且在buildLoginResp内对client.getIsEnableRefreshToken()的调用会直接 NPE。应比照login(...)方法对client做null、DisEnableStatusEnum.DISABLE、isEnableRefreshToken的校验。UserContext陈旧:直接从loginParameter.getExtra(AuthConstants.LOGIN_USER)取登录时快照,刷新窗口内用户角色/权限/禁用状态/租户变化不会生效,相当于权限回收被绕过。ClassCastException风险:Line 141、142 的强转没有类型判断(且取决于 Sa-Token 的序列化器,反序列化后多为LinkedHashMap),建议增加instanceof保护并抛出业务异常。- 并发刷新未防护:
SaTempUtil.deleteToken(refreshToken)在 Line 152 才执行,同一 refresh token 的并发请求都能通过parseToken得到有效结果,会生成两份新会话。建议在parseToken成功后立即deleteToken(并结合删除结果判定),再进入buildLoginResp。- 过期与无效无法区分:
parseToken返回null时无法区分“过期”与“无效”,前端难以针对性提示;建议引入专用错误码。上述问题在前次评审已提出,本次 commit 未体现修复。
🛠️ 建议修改骨架
`@Override` public LoginResp refreshToken(String refreshToken) { Object refreshTokenInfo = SaTempUtil.parseToken(refreshToken); - if (refreshTokenInfo == null) { - throw new BusinessException("无效的Refresh Token"); - } - // 获取登录时设置的参数 - SaLoginParameter loginParameter = (SaLoginParameter) refreshTokenInfo; - UserContext userContext = (UserContext) loginParameter.getExtra(AuthConstants.LOGIN_USER); - UserContextHolder.setContext(userContext); - ClientResp client = clientService.getByClientId(userContext.getClientId()); - String oldToken = loginParameter.getToken(); - LoginResp loginResp = LoginHandler.buildLoginResp(loginParameter, userContext, client); - if (oldToken != null) { - // 删除原先的 Token - StpUtil.kickoutByTokenValue(oldToken); - } - // 删除原先的 Refresh token - SaTempUtil.deleteToken(refreshToken); - return loginResp; + if (!(refreshTokenInfo instanceof SaLoginParameter loginParameter)) { + throw new BusinessException("Refresh Token 已失效或无效"); + } + // 先删 refresh token,防止并发重复刷新 + SaTempUtil.deleteToken(refreshToken); + Object cached = loginParameter.getExtra(AuthConstants.LOGIN_USER); + Long userId = (cached instanceof UserContext uc) ? uc.getId() : null; + if (userId == null) { + throw new BusinessException("Refresh Token 已失效或无效"); + } + // 重新加载用户与客户端,校验状态 + UserContext userContext = userService.reloadUserContext(userId); // 需新增/复用 + ClientResp client = clientService.getByClientId(userContext.getClientId()); + ValidationUtils.throwIfNull(client, "客户端不存在"); + ValidationUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(client.getStatus()), "客户端已禁用"); + ValidationUtils.throwIf(!Boolean.TRUE.equals(client.getIsEnableRefreshToken()), "客户端未启用 Refresh Token"); + UserContextHolder.setContext(userContext); + String oldToken = loginParameter.getToken(); + LoginResp loginResp = LoginHandler.buildLoginResp(loginParameter, userContext, client); + if (oldToken != null) { + StpUtil.kickoutByTokenValue(oldToken); + } + return loginResp; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java` around lines 134 - 154, The refreshToken method must harden several failure modes: after calling SaTempUtil.parseToken(refreshToken) validate the parsed object types with instanceof (ensure it's a SaLoginParameter and the extracted extra is a UserContext) and throw a BusinessException on mismatch; immediately attempt SaTempUtil.deleteToken(refreshToken) right after successful parse and proceed only if deletion indicates the token was valid to prevent concurrent double-refresh; re-fetch current user state (do not use the snapshot from loginParameter.getExtra) and set UserContextHolder with the fresh UserContext so role/permission/disabled/tenant changes are honored; validate client returned from clientService.getByClientId(userContext.getClientId()) for null, DisEnableStatusEnum.DISABLE and client.getIsEnableRefreshToken() before calling LoginHandler.buildLoginResp; and map parseToken null results to distinct BusinessException error codes/messages for “expired” vs “invalid” so the front-end can react appropriately.
🧹 Nitpick comments (2)
continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql (1)
6-8: 列定义的缩进不一致。Line 7 使用 4 空格缩进,Line 8 使用 2 空格缩进,建议保持一致以便阅读。
♻️ 建议修改
ALTER TABLE "public"."sys_client" ADD COLUMN "is_enable_refresh_token" bool NOT NULL DEFAULT false, - ADD COLUMN "refresh_token_timeout" int8 DEFAULT 2592000; + ADD COLUMN "refresh_token_timeout" int8 DEFAULT 2592000;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql` around lines 6 - 8, The ALTER TABLE block for "public"."sys_client" has inconsistent indentation between the two column lines; update the SQL so both ADD COLUMN lines for "is_enable_refresh_token" and "refresh_token_timeout" use the same indentation level (choose and apply a consistent number of spaces, e.g., 2 or 4) to improve readability around the ALTER TABLE "public"."sys_client" statement.continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java (1)
71-79: 字段注释与实际语义不完全一致。
refreshTokenTimeout的 Javadoc 写的是“默认 2,592,000”,但实际实现(LoginHandler.buildLoginResp)使用的语义是:当值<= 0或为null时,回退为timeout。建议与ClientResp中@Schema(description = "Refresh Token 有效期(单位:秒; 小于0,则和 Token 有效期相同)")的描述保持一致,避免读者误解。♻️ 建议修改
/** - * Refresh Token 有效期(单位:秒,默认 2,592,000) + * Refresh Token 有效期(单位:秒,小于等于 0 或为 null 时取 Token 有效期) */ private Long refreshTokenTimeout;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java` around lines 71 - 79, Update the Javadoc for the ClientDO.refreshTokenTimeout field to match its actual semantics used in LoginHandler.buildLoginResp and the description in ClientResp: state the unit (seconds), and that null or values <= 0 mean "use the same timeout as the access Token" (i.e., fallback to token timeout) rather than claiming a fixed default of 2,592,000; reference the field name refreshTokenTimeout and related LoginHandler.buildLoginResp behavior so readers understand the fallback rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java`:
- Around line 140-144: The refresh-token flow risks ClassCastException because
UserContext is stored in extraData and later cast via (UserContext)
loginParameter.getExtra(AuthConstants.LOGIN_USER) (see
AuthServiceImpl#refreshToken and AbstractLoginHandler), and storing full
UserContext in the refresh token bloats data and stale permissions; fix by
storing only the minimal identifier(s) (e.g., userId) in
loginParameter.extraData instead of the full UserContext when building the token
in AbstractLoginHandler (remove putting UserContext into extraData), and update
AuthServiceImpl#refreshToken to read the userId from
loginParameter.getExtra(AuthConstants.LOGIN_USER) (or a new key like
AuthConstants.LOGIN_USER_ID), then rehydrate the full UserContext from the
database or user service before building the new token, ensuring any
parse/deserialization via SaTempUtil.parseToken / SaLoginParameter never
requires casting a deserialized map to UserContext.
---
Outside diff comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.java`:
- Around line 32-45: LoginResp was refactored into a base class and now only
contains tenantId while token/expiresIn moved to SingleTokenLoginResp and
accessToken/refreshToken to DoubleTokenLoginResp; verify and update consumers
and docs: ensure frontend deserialization handles polymorphism or uses the
correct subtype (SingleTokenLoginResp/DoubleTokenLoginResp) depending on
client.getIsEnableRefreshToken, update API docs to state responses are
subtype-specific rather than LoginResp, and add a CHANGELOG entry describing
this breaking change so integrators know to switch from accessing
token/expiresIn on LoginResp to the appropriate subclass fields.
---
Duplicate comments:
In
`@continew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.java`:
- Around line 134-154: The refreshToken method must harden several failure
modes: after calling SaTempUtil.parseToken(refreshToken) validate the parsed
object types with instanceof (ensure it's a SaLoginParameter and the extracted
extra is a UserContext) and throw a BusinessException on mismatch; immediately
attempt SaTempUtil.deleteToken(refreshToken) right after successful parse and
proceed only if deletion indicates the token was valid to prevent concurrent
double-refresh; re-fetch current user state (do not use the snapshot from
loginParameter.getExtra) and set UserContextHolder with the fresh UserContext so
role/permission/disabled/tenant changes are honored; validate client returned
from clientService.getByClientId(userContext.getClientId()) for null,
DisEnableStatusEnum.DISABLE and client.getIsEnableRefreshToken() before calling
LoginHandler.buildLoginResp; and map parseToken null results to distinct
BusinessException error codes/messages for “expired” vs “invalid” so the
front-end can react appropriately.
---
Nitpick comments:
In
`@continew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sql`:
- Around line 6-8: The ALTER TABLE block for "public"."sys_client" has
inconsistent indentation between the two column lines; update the SQL so both
ADD COLUMN lines for "is_enable_refresh_token" and "refresh_token_timeout" use
the same indentation level (choose and apply a consistent number of spaces,
e.g., 2 or 4) to improve readability around the ALTER TABLE
"public"."sys_client" statement.
In
`@continew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.java`:
- Around line 71-79: Update the Javadoc for the ClientDO.refreshTokenTimeout
field to match its actual semantics used in LoginHandler.buildLoginResp and the
description in ClientResp: state the unit (seconds), and that null or values <=
0 mean "use the same timeout as the access Token" (i.e., fallback to token
timeout) rather than claiming a fixed default of 2,592,000; reference the field
name refreshTokenTimeout and related LoginHandler.buildLoginResp behavior so
readers understand the fallback rule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 541cf9ea-9c1a-483e-a652-7dcef702a177
📒 Files selected for processing (15)
continew-server/src/main/resources/db/changelog/db.changelog-master.yamlcontinew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sqlcontinew-server/src/main/resources/db/changelog/postgresql/V4.2.0/v4.2.0_1_change.sqlcontinew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/LoginHandler.javacontinew-system/src/main/java/top/continew/admin/auth/constant/AuthConstants.javacontinew-system/src/main/java/top/continew/admin/auth/controller/AuthController.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/DoubleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/LoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.javacontinew-system/src/main/java/top/continew/admin/auth/service/AuthService.javacontinew-system/src/main/java/top/continew/admin/auth/service/impl/AuthServiceImpl.javacontinew-system/src/main/java/top/continew/admin/system/model/entity/ClientDO.javacontinew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.javacontinew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java
✅ Files skipped from review due to trivial changes (2)
- continew-system/src/main/java/top/continew/admin/auth/constant/AuthConstants.java
- continew-server/src/main/resources/db/changelog/mysql/V4.2.0/v4.2.0_1_change.sql
🚧 Files skipped from review as they are similar to previous changes (5)
- continew-system/src/main/java/top/continew/admin/auth/service/AuthService.java
- continew-system/src/main/java/top/continew/admin/system/model/req/ClientReq.java
- continew-system/src/main/java/top/continew/admin/auth/model/resp/SingleTokenLoginResp.java
- continew-system/src/main/java/top/continew/admin/system/model/resp/ClientResp.java
- continew-system/src/main/java/top/continew/admin/auth/controller/AuthController.java
| Map<String, Object> extraData = BeanUtil.beanToMap(new UserExtraContext(ServletUtils.getRequest())); | ||
| extraData.put(AuthConstants.LOGIN_USER, userContext); | ||
| loginParameter.setExtraData(extraData); | ||
| UserContextHolder.setContext(userContext); | ||
| return LoginResp.builder() | ||
| .token(StpUtil.getTokenValue()) | ||
| .tenantId(TenantContextHolder.isTenantEnabled() ? TenantContextHolder.getTenantId() : null) | ||
| .build(); | ||
| return LoginHandler.buildLoginResp(loginParameter, userContext, client); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 确认 Sa-Token 的序列化器配置,判断 getExtra 取出的实际类型
rg -nP --type=java -C3 'SaJsonTemplate|SaSerializerTemplate|sa-token.*serializer'
rg -nP --type=yaml -C3 'sa-token|satoken'Repository: continew-org/continew-admin
Length of output: 3762
🏁 Script executed:
# Find AuthServiceImpl and check refreshToken method
fd -type f -name "AuthServiceImpl.java" | head -5Repository: continew-org/continew-admin
Length of output: 242
🏁 Script executed:
# Find UserContext class definition
fd -type f -name "UserContext.java" | head -5Repository: continew-org/continew-admin
Length of output: 242
🏁 Script executed:
# Check Sa-Token version and serializer configuration in pom.xml
rg -nP --type=xml 'sa-token|satoken' | head -20Repository: continew-org/continew-admin
Length of output: 343
🏁 Script executed:
# Check for Sa-Token serializer configuration in Java code
rg -nP --type=java 'SaSerializerTemplate|SaJsonTemplate|setJsonSerializer|parseToken' | head -20Repository: continew-org/continew-admin
Length of output: 446
🏁 Script executed:
# Look for refreshToken implementation and the cast operation
rg -nP --type=java -A5 'refreshToken.*LoginParameter' | head -30Repository: continew-org/continew-admin
Length of output: 53
🏁 Script executed:
# Read AuthServiceImpl.java around line 136-142 for refreshToken
find . -name "AuthServiceImpl.java" -exec cat -n {} \; | sed -n '130,150p'Repository: continew-org/continew-admin
Length of output: 1157
🏁 Script executed:
# Find UserContext class
find . -name "UserContext.java" -exec cat -n {} \;Repository: continew-org/continew-admin
Length of output: 4868
🏁 Script executed:
# Find UserExtraContext class
find . -name "UserExtraContext.java" -exec cat -n {} \;Repository: continew-org/continew-admin
Length of output: 2618
🏁 Script executed:
# Check Sa-Token dependency version and check for serializer configuration
cat continew-common/pom.xml | grep -A2 -B2 "sa-token"Repository: continew-org/continew-admin
Length of output: 249
🏁 Script executed:
# Look for AbstractLoginHandler.java to verify lines 140-144
find . -name "AbstractLoginHandler.java" -exec cat -n {} \;Repository: continew-org/continew-admin
Length of output: 7798
🏁 Script executed:
# Find SaTempUtil or check for custom Sa-Token serializer configuration
find . -name "*SaTempUtil*" -o -name "*satoken*" -type f | grep -i java | head -10Repository: continew-org/continew-admin
Length of output: 53
🏁 Script executed:
# Search for Sa-Token configuration/customization in the starter package
find . -path "*/continew-starter-auth-satoken/*" -name "*.java" | head -20Repository: continew-org/continew-admin
Length of output: 53
🏁 Script executed:
# Check if there's any custom JSON deserialization for LoginParameter
rg -nP --type=java "JsonDeserializer|@JsonDeserialize|readValue.*SaLoginParameter" | head -20Repository: continew-org/continew-admin
Length of output: 53
🏁 Script executed:
# Check continew pom.xml for sa-token version specification
find . -name "pom.xml" -exec grep -l "sa-token" {} \; | xargs grep -A5 "sa-token"Repository: continew-org/continew-admin
Length of output: 325
UserContext 反序列化存在类型强转风险。
代码在 AuthServiceImpl#refreshToken 第 142 行直接进行类型强转:(UserContext) loginParameter.getExtra(AuthConstants.LOGIN_USER)。当 Sa-Token 通过 SaTempUtil.parseToken() 反序列化 SaLoginParameter 时,若使用默认的 JSON 序列化器,extraData 中的 UserContext 对象将被反序列化为 LinkedHashMap,导致类型强转失败抛出 ClassCastException。
此外,将完整的 UserContext(包含权限集合、角色集合等大量数据)存储在 refresh token 中,会显著增加存储占用,且可能导致权限不及时更新的问题。建议仅在 refresh token 中保存 userId 等必要字段,在刷新时从数据库重建完整的 UserContext,确保权限和角色信息始终为最新状态。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@continew-system/src/main/java/top/continew/admin/auth/AbstractLoginHandler.java`
around lines 140 - 144, The refresh-token flow risks ClassCastException because
UserContext is stored in extraData and later cast via (UserContext)
loginParameter.getExtra(AuthConstants.LOGIN_USER) (see
AuthServiceImpl#refreshToken and AbstractLoginHandler), and storing full
UserContext in the refresh token bloats data and stale permissions; fix by
storing only the minimal identifier(s) (e.g., userId) in
loginParameter.extraData instead of the full UserContext when building the token
in AbstractLoginHandler (remove putting UserContext into extraData), and update
AuthServiceImpl#refreshToken to read the userId from
loginParameter.getExtra(AuthConstants.LOGIN_USER) (or a new key like
AuthConstants.LOGIN_USER_ID), then rehydrate the full UserContext from the
database or user service before building the new token, ensuring any
parse/deserialization via SaTempUtil.parseToken / SaLoginParameter never
requires casting a deserialized map to UserContext.
3b0f6e7 to
2a22d06
Compare
PR 类型
PR 目的
解决方案
PR 测试
Changelog
其他信息
提交前确认
Summary by CodeRabbit