优化
This commit is contained in:
+3
-3
@@ -3,7 +3,7 @@
|
||||
## 职责边界
|
||||
|
||||
- 负责账号凭据、登录、Access/Refresh Token 生命周期、退出、密码变更及商户注册协作。
|
||||
- 租户、成员资料、角色和权限属于 `iam/`;Auth 只通过 IAM 客户端加载授权快照和创建注册结果。
|
||||
- 客户主档、成员资料、角色和权限属于 `iam/`;Auth 只通过 IAM 客户端加载授权快照和创建注册结果。现有账号/JWT 中的 `tenant_id > 0` 当前是客户 ID,`tenant_id=0` 是平台技术作用域;字段名保持兼容,不代表已经存在真实租户层。
|
||||
|
||||
## 快速索引
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
| 商户注册协作 | `service/MerchantRegistrationService.java` |
|
||||
| IAM 调用与授权快照 | `client/IamAuthorizationClient.java`、`IamOnboardingClient.java`、`AuthorizationSnapshot.java` |
|
||||
| 账号与 Refresh Token 持久化 | `entity/`、`mapper/`、`repository/` |
|
||||
| 请求/响应模型 | `dto/AccountManagementDtos.java`;创建可指定初始状态,`AccountSummary` 返回最后登录时间、租户、成员与应用端 |
|
||||
| 请求/响应模型 | `dto/AccountManagementDtos.java`;创建可指定初始状态,`AccountSummary` 返回最后登录时间、兼容字段 `tenantId`、成员与应用端 |
|
||||
| 表结构与一期账号 | `src/main/resources/db/auth.sql`、`phase1-admin-accounts.sql` |
|
||||
| 配置与测试 | `src/main/resources/application.yml`、`src/test/java/com/baiyue/auth/service/` |
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
- 修改 Token Claim、会话 Redis Key、过期或撤销逻辑时同步检查 `common-security/`、`gateway/` 和 IAM 授权快照。
|
||||
- 注册流程跨 Auth/IAM,失败补偿和幂等必须成对检查;不要跨库写 IAM 表。
|
||||
- 内部账号创建允许相同账号 ID 与相同归属幂等重试;不同身份占用同一 ID 必须冲突。内部接口只接收 IAM 的服务令牌,不直接暴露给前端。
|
||||
- 平台统一用户管理跨租户移动成员时由 IAM 调用 `PUT /internal/auth/accounts/{id}/scope` 同步 Auth 默认租户、成员和应用端;修改该契约必须同时检查 `iam/client/AuthAccountClient.java` 与 `PlatformUserManagementService.java`。
|
||||
- 平台统一用户管理变更账号客户归属时由 IAM 调用 `PUT /internal/auth/accounts/{id}/scope` 同步 Auth 的客户隔离兼容字段、成员和应用端;修改该历史契约必须同时检查 `iam/client/AuthAccountClient.java` 与 `PlatformUserManagementService.java`。
|
||||
- 登录、刷新等业务失败仍是 HTTP 200 + 非 `"200"` body code;Security 入口产生的真实 401 保留给客户端 Token 刷新流程。
|
||||
- 从父目录运行 `mvn -pl auth -am test`,认证接口变更再运行相关 `scripts/` 回归。
|
||||
|
||||
@@ -10,6 +10,8 @@ import feign.FeignException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 授权快照加载器,调用 IAM 服务并校验返回身份上下文,为认证令牌组装权限声明。
|
||||
*/
|
||||
@@ -42,10 +44,17 @@ public class AuthorizationLoader {
|
||||
}
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM 权限服务暂时不可用");
|
||||
}
|
||||
if (response.data() == null) {
|
||||
AuthorizationSnapshot snapshot = response.data();
|
||||
if (snapshot == null) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "账号没有有效的 IAM 身份或权限");
|
||||
}
|
||||
return response.data();
|
||||
if (!Objects.equals(account.getId(), snapshot.getUserId())
|
||||
|| !Objects.equals(account.getDefaultTenantId(), snapshot.getTenantId())
|
||||
|| !Objects.equals(account.getDefaultMembershipId(), snapshot.getMembershipId())
|
||||
|| !Objects.equals(account.getDefaultAppCode(), snapshot.getAppCode())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "IAM 授权快照身份与账号归属不一致");
|
||||
}
|
||||
return snapshot;
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM 权限服务暂时不可用");
|
||||
} catch (BizException exception) {
|
||||
|
||||
@@ -25,7 +25,15 @@ public class InternalAccountController {
|
||||
private String configuredToken;
|
||||
private byte[] tokenBytes;
|
||||
|
||||
@PostConstruct void init() { tokenBytes = configuredToken.getBytes(StandardCharsets.UTF_8); }
|
||||
@PostConstruct
|
||||
void init() {
|
||||
byte[] configured = configuredToken == null
|
||||
? new byte[0] : configuredToken.getBytes(StandardCharsets.UTF_8);
|
||||
if (configured.length < 32) {
|
||||
throw new IllegalStateException("INTERNAL_SERVICE_TOKEN 至少需要 32 字节");
|
||||
}
|
||||
tokenBytes = configured;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<AccountSummary> create(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.baiyue.auth.client;
|
||||
|
||||
import com.baiyue.auth.entity.AuthAccount;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AuthorizationLoaderTest {
|
||||
private static final String INTERNAL_TOKEN = "test-internal-service-token-at-least-32-bytes";
|
||||
|
||||
@Mock private IamAuthorizationClient client;
|
||||
@InjectMocks private AuthorizationLoader loader;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(loader, "internalToken", INTERNAL_TOKEN);
|
||||
loader.validateConfiguration();
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsSnapshotWhenEchoedIdentityMatchesAccount() {
|
||||
AuthAccount account = account();
|
||||
AuthorizationSnapshot snapshot = snapshot(42L, 10001L, 9L, "MERCHANT");
|
||||
when(client.load(eq(INTERNAL_TOKEN), any())).thenReturn(ApiResponse.success(snapshot));
|
||||
|
||||
assertSame(snapshot, loader.load(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEveryMismatchedEchoedIdentityField() {
|
||||
AuthAccount account = account();
|
||||
|
||||
assertIdentityMismatch(account, snapshot(43L, 10001L, 9L, "MERCHANT"));
|
||||
assertIdentityMismatch(account, snapshot(42L, 10002L, 9L, "MERCHANT"));
|
||||
assertIdentityMismatch(account, snapshot(42L, 10001L, 10L, "MERCHANT"));
|
||||
assertIdentityMismatch(account, snapshot(42L, 10001L, 9L, "PLATFORM"));
|
||||
}
|
||||
|
||||
private void assertIdentityMismatch(AuthAccount account, AuthorizationSnapshot snapshot) {
|
||||
when(client.load(eq(INTERNAL_TOKEN), any())).thenReturn(ApiResponse.success(snapshot));
|
||||
|
||||
BizException exception = assertThrows(BizException.class, () -> loader.load(account));
|
||||
|
||||
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
|
||||
}
|
||||
|
||||
private AuthAccount account() {
|
||||
AuthAccount account = new AuthAccount();
|
||||
account.setId(42L);
|
||||
account.setDefaultTenantId(10001L);
|
||||
account.setDefaultMembershipId(9L);
|
||||
account.setDefaultAppCode("MERCHANT");
|
||||
return account;
|
||||
}
|
||||
|
||||
private AuthorizationSnapshot snapshot(Long userId, Long tenantId, Long membershipId, String appCode) {
|
||||
return new AuthorizationSnapshot(userId, tenantId, membershipId, appCode, appCode,
|
||||
Set.of("MERCHANT_ADMIN"), Set.of("merchant:profile:view"));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.auth.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class InternalAccountControllerConfigurationTest {
|
||||
@Test
|
||||
void rejectsEmptyAndShortInternalServiceTokens() {
|
||||
assertTokenRejected(null);
|
||||
assertTokenRejected("");
|
||||
assertTokenRejected("short-token");
|
||||
}
|
||||
|
||||
private void assertTokenRejected(String token) {
|
||||
InternalAccountController controller = new InternalAccountController();
|
||||
ReflectionTestUtils.setField(controller, "configuredToken", token);
|
||||
assertThrows(IllegalStateException.class, controller::init);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user