fix: project overseas authorization outside jwt

This commit is contained in:
2026-09-07 11:37:06 +08:00
parent 6b479d1a68
commit 4963889024
33 changed files with 382 additions and 92 deletions
@@ -63,6 +63,42 @@ public interface AuthorizationQueryMapper {
@Param("membershipId") long membershipId,
@Param("appCode") String appCode);
@Select("""
SELECT DISTINCT r.role_code
FROM iam_user u
JOIN iam_membership m ON m.user_id = u.id
JOIN iam_membership_role mr ON mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
JOIN iam_role r ON r.id = mr.role_id
WHERE u.auth_user_id = #{userId} AND u.status = 'ACTIVE' AND u.deleted = 0
AND m.tenant_id = #{tenantId} AND m.status = 'ACTIVE' AND m.deleted = 0
AND (r.tenant_id = 0 OR r.tenant_id = #{tenantId})
AND r.app_code = #{appCode} AND r.status = 'ACTIVE' AND r.deleted = 0
ORDER BY r.role_code
""")
List<String> findRolesByIdentity(@Param("userId") long userId,
@Param("tenantId") long tenantId,
@Param("appCode") String appCode);
@Select("""
SELECT DISTINCT p.permission_code
FROM iam_user u
JOIN iam_membership m ON m.user_id = u.id
JOIN iam_membership_role mr ON mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
JOIN iam_role r ON r.id = mr.role_id
JOIN iam_role_permission rp ON rp.role_id = r.id
JOIN iam_permission p ON p.id = rp.permission_id
WHERE u.auth_user_id = #{userId} AND u.status = 'ACTIVE' AND u.deleted = 0
AND m.tenant_id = #{tenantId} AND m.status = 'ACTIVE' AND m.deleted = 0
AND (r.tenant_id = 0 OR r.tenant_id = #{tenantId})
AND r.app_code = #{appCode} AND r.status = 'ACTIVE' AND r.deleted = 0
AND (p.tenant_id = 0 OR p.tenant_id = #{tenantId})
AND p.app_code = #{appCode} AND p.status = 'ACTIVE' AND p.deleted = 0
ORDER BY p.permission_code
""")
List<String> findPermissionsByIdentity(@Param("userId") long userId,
@Param("tenantId") long tenantId,
@Param("appCode") String appCode);
@Select("SELECT COALESCE(MAX(permission_version),0) FROM iam_security_subject_version WHERE auth_user_id=#{userId}")
long findPermissionVersion(@Param("userId") long userId);
@@ -7,6 +7,7 @@ import com.baiyue.common.event.EventPayloadHash;
import com.baiyue.common.event.EventTypes;
import com.baiyue.common.event.IamSecurityEventsV1;
import com.baiyue.common.event.RegionCode;
import com.baiyue.iam.mapper.AuthorizationQueryMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
@@ -14,12 +15,20 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/** 在 IAM 状态事务内递增安全版本并写入可靠事件发送箱。 */
@Service
public class SecurityEventOutboxService {
private static final List<String> OVERSEAS_AUTHORITY_PREFIXES = List.of(
"platform:overseas-warehouse:", "platform:overseas-business:",
"platform:overseas-master-data:", "merchant:warehouse:",
"merchant:cross-border:", "platform:finance:", "lastleg:");
@Resource private JdbcTemplate jdbc;
@Resource private ObjectMapper objectMapper;
@Resource private AuthorizationQueryMapper authorizationQueries;
public void userStatus(long authUserId, long tenantId, String appCode, String status) {
long version = bump(authUserId);
@@ -39,8 +48,13 @@ public class SecurityEventOutboxService {
public void permissionChanged(long authUserId, long tenantId, String appCode, String reason) {
long version = bump(authUserId);
Set<String> roles = Set.copyOf(authorizationQueries.findRolesByIdentity(authUserId, tenantId, appCode));
Set<String> authorities = authorizationQueries.findPermissionsByIdentity(authUserId, tenantId, appCode)
.stream().filter(permission -> permission != null && OVERSEAS_AUTHORITY_PREFIXES.stream()
.anyMatch(permission::startsWith)).collect(Collectors.toUnmodifiableSet());
write(EventTypes.IAM_PERMISSION_VERSION_CHANGED, "AUTHORIZATION", String.valueOf(authUserId), tenantId, version,
new IamSecurityEventsV1.PermissionVersionChanged(authUserId, tenantId, appCode, version, reason, Instant.now()));
new IamSecurityEventsV1.PermissionVersionChanged(authUserId, tenantId, appCode, version, reason,
Instant.now(), roles, authorities));
}
private long bump(long authUserId) {
@@ -80,7 +80,9 @@ class SecurityEventOutboxRelayTest {
private EventEnvelope envelope(RegionCode target) {
var value = new IamSecurityEventsV1.PermissionVersionChanged(
101L, 0L, "PLATFORM", 3L, "contract", Instant.parse("2026-08-25T07:00:00Z"));
101L, 0L, "PLATFORM", 3L, "contract", Instant.parse("2026-08-25T07:00:00Z"),
java.util.Set.of("PLATFORM_ADMIN"),
java.util.Set.of("platform:overseas-warehouse:last-mile-order:list"));
var payload = objectMapper.valueToTree(value);
return new EventEnvelope("00000000-0000-4000-8000-000000000081",
EventTypes.IAM_PERMISSION_VERSION_CHANGED, IamSecurityEventsV1.VERSION,
@@ -2,6 +2,7 @@ package com.baiyue.iam.service;
import com.baiyue.common.event.EventEnvelope;
import com.baiyue.common.event.IamSecurityEventsV1;
import com.baiyue.iam.mapper.AuthorizationQueryMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,6 +15,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -24,6 +27,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SecurityEventOutboxServiceTest {
@Mock JdbcTemplate jdbc;
@Mock AuthorizationQueryMapper authorizationQueries;
private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
private SecurityEventOutboxService service;
@@ -32,6 +36,7 @@ class SecurityEventOutboxServiceTest {
service = new SecurityEventOutboxService();
ReflectionTestUtils.setField(service, "jdbc", jdbc);
ReflectionTestUtils.setField(service, "objectMapper", objectMapper);
ReflectionTestUtils.setField(service, "authorizationQueries", authorizationQueries);
}
@Test
@@ -71,4 +76,27 @@ class SecurityEventOutboxServiceTest {
assertThat(payload.status()).isEqualTo(expectedSecurityStatus);
assertThat(envelope.aggregateVersion()).isEqualTo(3L);
}
@Test
void permissionEventCarriesOnlyTheOverseasAuthorizationSnapshot() throws Exception {
when(jdbc.queryForObject(anyString(), eq(Long.class), any())).thenReturn(7L);
when(authorizationQueries.findRolesByIdentity(101L, 0L, "ADMIN"))
.thenReturn(List.of("PLATFORM_ADMIN"));
when(authorizationQueries.findPermissionsByIdentity(101L, 0L, "ADMIN"))
.thenReturn(List.of("platform:overseas-warehouse:orders:view", "platform:iam:users:view",
"lastleg:orders:view"));
service.permissionChanged(101L, 0L, "ADMIN", "ROLE_CHANGED");
ArgumentCaptor<Object[]> parameters = ArgumentCaptor.forClass(Object[].class);
verify(jdbc).update(eq("INSERT INTO iam_security_event_outbox(event_id,event_type,aggregate_type,aggregate_id,payload_json) "
+ "VALUES (?,?,?,?,CAST(? AS JSON))"), parameters.capture());
EventEnvelope envelope = objectMapper.readValue(String.valueOf(parameters.getValue()[4]), EventEnvelope.class);
IamSecurityEventsV1.PermissionVersionChanged payload = objectMapper.treeToValue(
envelope.payload(), IamSecurityEventsV1.PermissionVersionChanged.class);
assertThat(payload.roles()).containsExactly("PLATFORM_ADMIN");
assertThat(payload.authorities()).containsExactlyInAnyOrder(
"platform:overseas-warehouse:orders:view", "lastleg:orders:view");
assertThat(payload.authorities()).doesNotContain("platform:iam:users:view");
}
}