fix: default register client id

This commit is contained in:
yuhaiming
2026-08-14 10:35:42 +08:00
parent 2f7597ed53
commit b789c1c07e
8 changed files with 141 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
workflow: hotfix
phase: build
context_compression: off
build_mode: direct
build_pause: null
subagent_dispatch: null
tdd_mode: direct
isolation: branch
verify_mode: light
auto_transition: true
base_ref: 2f7597ed53736b965191d4e48ee2c73d09ff71c0
design_doc: null
plan: null
verify_result: pending
verification_report: null
branch_status: pending
created_at: 2026-08-14
verified_at: null
archived: false

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-14

View File

@@ -0,0 +1,27 @@
## Context
`AuthController.register` 当前依赖 Spring MVC 的 `@Validated` 自动校验。该校验发生在控制器方法调用之前,所以方法体无法为缺失的 `clientId` 补默认值。
## Goals / Non-Goals
**Goals:**
- 空白 `clientId` 使用固定注册客户端 ID `32324a04c0175c8d61ac8a282553aea1`
- 默认值补充后仍执行 `RegisterBody` 的全部 Bean Validation 约束。
- 保持注册开关检查和注册服务调用顺序不变。
**Non-Goals:**
- 不调整 `RegisterBody` 的继承结构或其他登录字段。
- 不改变登录接口和认证客户端配置。
## Decisions
- 移除注册方法参数上的 `@Validated`,在方法体开始处补默认 `clientId` 后调用现有 `ValidatorUtils.validate(user)`。该工具已被认证控制器和各登录策略使用,异常处理行为与现有认证流程一致。
- 使用 `StringUtils.isBlank`,同时覆盖 `null`、空字符串和纯空白字符串。
- 通过 `AuthController` 单元测试观察注册服务收到的请求对象,验证公共接口行为。
## Risks / Trade-offs
- 固定客户端 ID 变更时需要同步修改代码和测试;本次按明确业务值实现。
- 手动校验依赖控制器方法内调用顺序;测试覆盖默认值补充发生在校验和业务调用之前。

View File

@@ -0,0 +1,25 @@
## Why
`/auth/register` 使用 `@Validated` 在进入控制器方法前校验 `RegisterBody`,缺少 `clientId` 的请求会直接返回“认证客户端id不能为空”因此控制器无法为注册请求补充系统默认客户端 ID。注册入口需要先补默认值再执行原有参数校验。
## What Changes
- 注册接口收到空白 `clientId` 时,将其设置为 `32324a04c0175c8d61ac8a282553aea1`
- 默认值补充完成后,通过现有 `ValidatorUtils` 执行 `RegisterBody` 的完整 Bean Validation。
- 增加控制器回归测试,覆盖默认客户端 ID 和注册服务调用。
## Capabilities
### New Capabilities
- `auth-registration`: 为已有注册入口补充默认认证客户端 ID 的行为规范。
### Modified Capabilities
无。仓库当前没有注册流程的既有 OpenSpec。
## Impact
- 影响 `water-admin` 中的 `/auth/register` 控制器及其单元测试。
- `/auth/register` 允许调用方省略或传入空白 `clientId`,其他注册参数约束保持不变。
- 不新增依赖,不修改数据库结构,也不新增公共端点。

View File

@@ -0,0 +1,11 @@
## ADDED Requirements
### Requirement: 注册请求使用默认认证客户端
系统 SHALL 在校验注册请求前,为缺失或空白的 `clientId` 设置默认值 `32324a04c0175c8d61ac8a282553aea1`,并在补值后执行 `RegisterBody` 的全部参数约束。
#### Scenario: 注册请求未提供客户端 ID
- **WHEN** 调用方提交的注册请求中 `clientId` 缺失或为空白
- **THEN** 系统将 `clientId` 设置为 `32324a04c0175c8d61ac8a282553aea1`
- **AND** 系统在调用注册服务前校验补值后的注册请求

View File

@@ -0,0 +1,4 @@
## 1. 注册默认客户端 ID
- [x] 1.1 在控制器单元测试中覆盖空白 `clientId` 自动补默认值的注册场景,并确认修改前失败。
- [x] 1.2 调整 `/auth/register` 的校验顺序,补默认 `clientId` 后执行完整参数校验,并运行相关测试。

View File

@@ -201,7 +201,11 @@ public class AuthController {
*/ */
@ApiEncrypt @ApiEncrypt
@PostMapping("/register") @PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) { public R<Void> register(@RequestBody RegisterBody user) {
if (StringUtils.isBlank(user.getClientId())) {
user.setClientId("32324a04c0175c8d61ac8a282553aea1");
}
ValidatorUtils.validate(user);
if (!configService.selectRegisterEnabled(user.getTenantId())) { if (!configService.selectRegisterEnabled(user.getTenantId())) {
return R.fail("当前系统没有开启注册功能!"); return R.fail("当前系统没有开启注册功能!");
} }

View File

@@ -1,20 +1,28 @@
package org.dromara.web.controller; package org.dromara.web.controller;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.AccountCancelBody; import org.dromara.common.core.domain.model.AccountCancelBody;
import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.service.ISysConfigService;
import org.dromara.web.service.AccountCancellationService; import org.dromara.web.service.AccountCancellationService;
import org.dromara.web.service.SysRegisterService;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.support.GenericApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@Tag("dev") @Tag("dev")
@@ -23,6 +31,12 @@ class AuthControllerUnitTest {
@Mock @Mock
private AccountCancellationService accountCancellationService; private AccountCancellationService accountCancellationService;
@Mock
private SysRegisterService registerService;
@Mock
private ISysConfigService configService;
@Test @Test
void cancelAccount_checksLoginCancelsAccountAndLogsOut() { void cancelAccount_checksLoginCancelsAccountAndLogsOut() {
AuthController controller = new AuthController( AuthController controller = new AuthController(
@@ -52,4 +66,38 @@ class AuthControllerUnitTest {
stpUtil.verify(() -> StpUtil.logout(100L)); stpUtil.verify(() -> StpUtil.logout(100L));
} }
} }
@Test
void register_defaultsClientIdWhenMissing() {
AuthController controller = new AuthController(
null,
null,
registerService,
configService,
null,
null,
null,
null,
null,
accountCancellationService
);
RegisterBody user = new RegisterBody();
user.setUsername("register-user");
user.setPassword("12345");
user.setGrantType("password");
when(configService.selectRegisterEnabled(user.getTenantId())).thenReturn(true);
try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
GenericApplicationContext context = new GenericApplicationContext()) {
context.getBeanFactory().registerSingleton("validator", validatorFactory.getValidator());
context.refresh();
new SpringUtil().setApplicationContext(context);
R<Void> result = controller.register(user);
assertThat(result.getCode()).isEqualTo(200);
assertThat(user.getClientId()).isEqualTo("32324a04c0175c8d61ac8a282553aea1");
verify(registerService).register(user);
}
}
} }