This commit is contained in:
2026-08-05 16:26:10 +08:00
parent 06f7f9d2f2
commit 5ba9716e12
155 changed files with 2883 additions and 551 deletions
@@ -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"));
}
}
@@ -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);
}
}