fix(overseas): support projected authorization headers

This commit is contained in:
2026-09-08 21:59:13 +08:00
parent 55fda1f616
commit a060f8dca8
7 changed files with 122 additions and 60 deletions
+1
View File
@@ -40,6 +40,7 @@ spring:
connect-timeout: 3000 connect-timeout: 3000
read-timeout: 5000 read-timeout: 5000
server: server:
max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB}
shutdown: graceful shutdown: graceful
management: management:
endpoints: endpoints:
+1
View File
@@ -24,6 +24,7 @@ x-overseas-common: &overseas-common
DEPLOYMENT_CELL: ${OVERSEAS_DEPLOYMENT_CELL:-test-overseas} DEPLOYMENT_CELL: ${OVERSEAS_DEPLOYMENT_CELL:-test-overseas}
DATA_RESIDENCY: OVERSEAS DATA_RESIDENCY: OVERSEAS
SPRING_SQL_INIT_MODE: never SPRING_SQL_INIT_MODE: never
SERVER_MAX_HTTP_REQUEST_HEADER_SIZE: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:-32KB}
TZ: Asia/Shanghai TZ: Asia/Shanghai
logging: logging:
# 应用只写 stdout/stderr;json-file 兼容宝塔查看,大小与数量均受控。 # 应用只写 stdout/stderr;json-file 兼容宝塔查看,大小与数量均受控。
@@ -9,6 +9,9 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class OverseasSecurityValidationDeploymentContractTest { class OverseasSecurityValidationDeploymentContractTest {
private static final String HEADER_SIZE =
"max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB}";
@Test @Test
void gatewayLocalJwtDefaultsMatchAuthAndLastleg() throws Exception { void gatewayLocalJwtDefaultsMatchAuthAndLastleg() throws Exception {
String gatewayApplication = read("gateway-overseas/src/main/resources/application.yml"); String gatewayApplication = read("gateway-overseas/src/main/resources/application.yml");
@@ -64,6 +67,15 @@ class OverseasSecurityValidationDeploymentContractTest {
"OVERSEAS_SECURITY_SERVICE_URL", "OVERSEAS_SECURITY_EVENT_CONSUMER_URL"); "OVERSEAS_SECURITY_SERVICE_URL", "OVERSEAS_SECURITY_EVENT_CONSUMER_URL");
} }
@Test
void overseasAuthorizationSnapshotHeadersFitEveryServletService() throws Exception {
assertThat(read("config/nacos/overseas/baiyue-common.yml")).contains(HEADER_SIZE);
assertThat(read("lastleg-service/src/main/resources/application.yml")).contains(HEADER_SIZE);
assertThat(read("warehouse-overseas-service/src/main/resources/application.yml")).contains(HEADER_SIZE);
assertThat(read("deploy/docker-compose.overseas.yml"))
.contains("SERVER_MAX_HTTP_REQUEST_HEADER_SIZE: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:-32KB}");
}
private static String read(String relative) throws Exception { private static String read(String relative) throws Exception {
Path path = Path.of(relative); Path path = Path.of(relative);
if (!Files.exists(path)) path = Path.of("..").resolve(relative); if (!Files.exists(path)) path = Path.of("..").resolve(relative);
@@ -1,5 +1,6 @@
server: server:
port: ${LASTLEG_PORT:9303} port: ${LASTLEG_PORT:9303}
max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB}
spring: spring:
servlet: servlet:
multipart: multipart:
+84 -60
View File
@@ -37,62 +37,95 @@ UPDATE ll_security_projection
SET authorities_json = JSON_ARRAY() SET authorities_json = JSON_ARRAY()
WHERE authorities_json IS NULL; WHERE authorities_json IS NULL;
-- 9000000001 是本地平台自定义管理员账号;权限来源以 IAM 当前角色授权为准。 -- 从 IAM 当前平台成员重建 USER 与 AUTHORIZATION 投影,避免把修复绑定到某个环境账号 ID。
INSERT INTO ll_security_projection ( INSERT INTO ll_security_projection (
aggregate_type, aggregate_id, tenant_id, security_status, aggregate_type, aggregate_id, tenant_id, security_status,
permission_version, roles_json, authorities_json, aggregate_version, permission_version, roles_json, authorities_json, aggregate_version,
payload_hash, last_event_id, changed_at, updated_at payload_hash, last_event_id, changed_at, updated_at
) )
SELECT SELECT
'AUTHORIZATION', '9000000001', 0, NULL, 'USER', CAST(u.auth_user_id AS CHAR), 0,
COALESCE((SELECT MAX(permission_version) IF(u.status = 'ACTIVE' AND m.status = 'ACTIVE', 'ACTIVE', 'DISABLED'),
FROM baiyue_iam.iam_security_subject_version COALESCE(v.permission_version, 0), JSON_ARRAY(), JSON_ARRAY(),
WHERE auth_user_id = 9000000001), 0), COALESCE(v.permission_version, 0),
(SELECT JSON_ARRAYAGG(role_code) SHA2(CONCAT('environment-repair:', u.auth_user_id, ':user:', COALESCE(v.permission_version, 0)), 256),
FROM ( CONCAT('repair-', u.auth_user_id, '-u-', DATE_FORMAT(NOW(3), '%Y%m%d%H%i%s%f')),
SELECT DISTINCT r.role_code
FROM baiyue_iam.iam_user u
JOIN baiyue_iam.iam_membership m ON m.user_id = u.id
JOIN baiyue_iam.iam_membership_role mr
ON mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
JOIN baiyue_iam.iam_role r ON r.id = mr.role_id
WHERE u.auth_user_id = 9000000001
AND m.id = 9000000003 AND m.tenant_id = 0
AND m.status = 'ACTIVE' AND m.deleted = 0
AND r.app_code = 'PLATFORM'
AND (r.tenant_id = 0 OR r.tenant_id = m.tenant_id)
AND r.status = 'ACTIVE' AND r.deleted = 0
ORDER BY r.role_code
) roles),
(SELECT JSON_ARRAYAGG(permission_code)
FROM (
SELECT DISTINCT p.permission_code
FROM baiyue_iam.iam_user u
JOIN baiyue_iam.iam_membership m ON m.user_id = u.id
JOIN baiyue_iam.iam_membership_role mr
ON mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
JOIN baiyue_iam.iam_role r ON r.id = mr.role_id
JOIN baiyue_iam.iam_role_permission rp
ON rp.role_id = r.id AND rp.tenant_id = r.tenant_id
JOIN baiyue_iam.iam_permission p
ON p.id = rp.permission_id AND p.tenant_id = rp.tenant_id
WHERE u.auth_user_id = 9000000001
AND m.id = 9000000003 AND m.tenant_id = 0
AND m.status = 'ACTIVE' AND m.deleted = 0
AND r.app_code = 'PLATFORM'
AND (r.tenant_id = 0 OR r.tenant_id = m.tenant_id)
AND r.status = 'ACTIVE' AND r.deleted = 0
AND p.app_code = 'PLATFORM'
AND (p.tenant_id = 0 OR p.tenant_id = m.tenant_id)
AND p.status = 'ACTIVE' AND p.deleted = 0
ORDER BY p.permission_code
) permissions),
COALESCE((SELECT MAX(permission_version)
FROM baiyue_iam.iam_security_subject_version
WHERE auth_user_id = 9000000001), 0),
SHA2('local-repair:9000000001:authorization', 256),
CONCAT('local-repair-9000000001-', DATE_FORMAT(NOW(3), '%Y%m%d%H%i%s%f')),
NOW(3), NOW(3) NOW(3), NOW(3)
FROM baiyue_iam.iam_user u
JOIN baiyue_iam.iam_membership m
ON m.user_id = u.id AND m.tenant_id = 0 AND m.deleted = 0
LEFT JOIN baiyue_iam.iam_security_subject_version v
ON v.auth_user_id = u.auth_user_id
WHERE u.auth_user_id IS NOT NULL AND u.deleted = 0
AND EXISTS (
SELECT 1
FROM baiyue_iam.iam_membership_role mr
JOIN baiyue_iam.iam_role r ON r.id = mr.role_id
WHERE mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
AND r.app_code = 'PLATFORM' AND r.status = 'ACTIVE' AND r.deleted = 0
)
ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id), security_status = VALUES(security_status),
permission_version = VALUES(permission_version), aggregate_version = VALUES(aggregate_version),
payload_hash = VALUES(payload_hash), last_event_id = VALUES(last_event_id),
changed_at = VALUES(changed_at), updated_at = NOW(3);
INSERT INTO ll_security_projection (
aggregate_type, aggregate_id, tenant_id, security_status,
permission_version, roles_json, authorities_json, aggregate_version,
payload_hash, last_event_id, changed_at, updated_at
)
SELECT
'AUTHORIZATION', CAST(u.auth_user_id AS CHAR), 0, NULL,
COALESCE(v.permission_version, 0),
COALESCE((
SELECT JSON_ARRAYAGG(r2.role_code)
FROM baiyue_iam.iam_user u2
JOIN baiyue_iam.iam_membership m2 ON m2.user_id = u2.id
JOIN baiyue_iam.iam_membership_role mr2
ON mr2.membership_id = m2.id AND mr2.tenant_id = m2.tenant_id
JOIN baiyue_iam.iam_role r2 ON r2.id = mr2.role_id
WHERE u2.auth_user_id = u.auth_user_id AND m2.tenant_id = 0
AND m2.status = 'ACTIVE' AND m2.deleted = 0
AND r2.app_code = 'PLATFORM' AND r2.status = 'ACTIVE' AND r2.deleted = 0
), JSON_ARRAY()),
COALESCE((
SELECT JSON_ARRAYAGG(p2.permission_code)
FROM baiyue_iam.iam_user u2
JOIN baiyue_iam.iam_membership m2 ON m2.user_id = u2.id
JOIN baiyue_iam.iam_membership_role mr2
ON mr2.membership_id = m2.id AND mr2.tenant_id = m2.tenant_id
JOIN baiyue_iam.iam_role r2 ON r2.id = mr2.role_id
JOIN baiyue_iam.iam_role_permission rp2
ON rp2.role_id = r2.id AND rp2.tenant_id = r2.tenant_id
JOIN baiyue_iam.iam_permission p2
ON p2.id = rp2.permission_id AND p2.tenant_id = rp2.tenant_id
WHERE u2.auth_user_id = u.auth_user_id AND m2.tenant_id = 0
AND m2.status = 'ACTIVE' AND m2.deleted = 0
AND r2.app_code = 'PLATFORM' AND r2.status = 'ACTIVE' AND r2.deleted = 0
AND p2.app_code = 'PLATFORM' AND p2.status = 'ACTIVE' AND p2.deleted = 0
AND (p2.permission_code LIKE 'platform:overseas-warehouse:%'
OR p2.permission_code LIKE 'platform:overseas-business:%'
OR p2.permission_code LIKE 'platform:overseas-master-data:%'
OR p2.permission_code LIKE 'platform:finance:%'
OR p2.permission_code LIKE 'lastleg:%')
), JSON_ARRAY()),
COALESCE(v.permission_version, 0),
SHA2(CONCAT('environment-repair:', u.auth_user_id, ':authorization:', COALESCE(v.permission_version, 0)), 256),
CONCAT('repair-', u.auth_user_id, '-a-', DATE_FORMAT(NOW(3), '%Y%m%d%H%i%s%f')),
NOW(3), NOW(3)
FROM baiyue_iam.iam_user u
JOIN baiyue_iam.iam_membership m
ON m.user_id = u.id AND m.tenant_id = 0 AND m.status = 'ACTIVE' AND m.deleted = 0
LEFT JOIN baiyue_iam.iam_security_subject_version v
ON v.auth_user_id = u.auth_user_id
WHERE u.auth_user_id IS NOT NULL AND u.status = 'ACTIVE' AND u.deleted = 0
AND EXISTS (
SELECT 1 FROM baiyue_iam.iam_membership_role mr
JOIN baiyue_iam.iam_role r ON r.id = mr.role_id
WHERE mr.membership_id = m.id AND mr.tenant_id = m.tenant_id
AND r.app_code = 'PLATFORM' AND r.status = 'ACTIVE' AND r.deleted = 0
)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id), tenant_id = VALUES(tenant_id),
permission_version = VALUES(permission_version), permission_version = VALUES(permission_version),
@@ -104,19 +137,10 @@ ON DUPLICATE KEY UPDATE
changed_at = VALUES(changed_at), changed_at = VALUES(changed_at),
updated_at = NOW(3); updated_at = NOW(3);
-- 若未来 IAM 为该账号创建了版本记录,按当前版本同步投影版本号。
UPDATE ll_security_projection p
JOIN baiyue_iam.iam_security_subject_version v
ON v.auth_user_id = CAST(p.aggregate_id AS UNSIGNED)
SET p.permission_version = v.permission_version,
p.aggregate_version = v.permission_version,
p.updated_at = NOW(3)
WHERE p.aggregate_type = 'AUTHORIZATION'
AND p.aggregate_id = '9000000001';
SELECT aggregate_type, aggregate_id, tenant_id, permission_version, SELECT aggregate_type, aggregate_id, tenant_id, permission_version,
JSON_LENGTH(roles_json) AS role_count, JSON_LENGTH(roles_json) AS role_count,
JSON_LENGTH(authorities_json) AS authority_count, JSON_LENGTH(authorities_json) AS authority_count,
updated_at updated_at
FROM ll_security_projection FROM ll_security_projection
WHERE aggregate_type = 'AUTHORIZATION' AND aggregate_id = '9000000001'; WHERE tenant_id = 0 AND aggregate_type IN ('USER', 'AUTHORIZATION')
ORDER BY CAST(aggregate_id AS UNSIGNED), aggregate_type;
@@ -0,0 +1,22 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
const sql = readFileSync(new URL('./db/security-projection-repair.sql', import.meta.url), 'utf8')
test('security projection repair derives every platform identity from IAM', () => {
assert.match(sql, /FROM baiyue_iam\.iam_user u/)
assert.match(sql, /CAST\(u\.auth_user_id AS CHAR\)/)
assert.doesNotMatch(sql, /9000000001|9000000003/)
})
test('security projection repair rebuilds user status and overseas authorization', () => {
assert.match(sql, /'USER'/)
assert.match(sql, /'AUTHORIZATION'/)
assert.match(sql, /platform:overseas-warehouse:/)
assert.match(sql, /platform:overseas-business:/)
assert.match(sql, /platform:overseas-master-data:/)
assert.match(sql, /platform:finance:/)
assert.match(sql, /lastleg:/)
assert.match(sql, /CONCAT\('repair-', u\.auth_user_id, '-[ua]-'/)
})
@@ -1,5 +1,6 @@
server: server:
port: ${WAREHOUSE_PORT:9305} port: ${WAREHOUSE_PORT:9305}
max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB}
spring: spring:
sql: sql:
init: init: