diff --git a/config/nacos/overseas/baiyue-common.yml b/config/nacos/overseas/baiyue-common.yml index a9e0a75f3..a5e5cb364 100644 --- a/config/nacos/overseas/baiyue-common.yml +++ b/config/nacos/overseas/baiyue-common.yml @@ -40,6 +40,7 @@ spring: connect-timeout: 3000 read-timeout: 5000 server: + max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB} shutdown: graceful management: endpoints: diff --git a/deploy/docker-compose.overseas.yml b/deploy/docker-compose.overseas.yml index 274f284f9..3d57fd369 100644 --- a/deploy/docker-compose.overseas.yml +++ b/deploy/docker-compose.overseas.yml @@ -24,6 +24,7 @@ x-overseas-common: &overseas-common DEPLOYMENT_CELL: ${OVERSEAS_DEPLOYMENT_CELL:-test-overseas} DATA_RESIDENCY: OVERSEAS SPRING_SQL_INIT_MODE: never + SERVER_MAX_HTTP_REQUEST_HEADER_SIZE: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:-32KB} TZ: Asia/Shanghai logging: # 应用只写 stdout/stderr;json-file 兼容宝塔查看,大小与数量均受控。 diff --git a/gateway-overseas/src/test/java/com/baiyue/gateway/config/OverseasSecurityValidationDeploymentContractTest.java b/gateway-overseas/src/test/java/com/baiyue/gateway/config/OverseasSecurityValidationDeploymentContractTest.java index e377d6422..a5964dbe4 100644 --- a/gateway-overseas/src/test/java/com/baiyue/gateway/config/OverseasSecurityValidationDeploymentContractTest.java +++ b/gateway-overseas/src/test/java/com/baiyue/gateway/config/OverseasSecurityValidationDeploymentContractTest.java @@ -9,6 +9,9 @@ import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; class OverseasSecurityValidationDeploymentContractTest { + private static final String HEADER_SIZE = + "max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB}"; + @Test void gatewayLocalJwtDefaultsMatchAuthAndLastleg() throws Exception { 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"); } + @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 { Path path = Path.of(relative); if (!Files.exists(path)) path = Path.of("..").resolve(relative); diff --git a/lastleg-service/src/main/resources/application.yml b/lastleg-service/src/main/resources/application.yml index 3213abf67..bfa497633 100644 --- a/lastleg-service/src/main/resources/application.yml +++ b/lastleg-service/src/main/resources/application.yml @@ -1,5 +1,6 @@ server: port: ${LASTLEG_PORT:9303} + max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB} spring: servlet: multipart: diff --git a/scripts/db/security-projection-repair.sql b/scripts/db/security-projection-repair.sql index 719e86032..6ae257773 100644 --- a/scripts/db/security-projection-repair.sql +++ b/scripts/db/security-projection-repair.sql @@ -37,62 +37,95 @@ UPDATE ll_security_projection SET authorities_json = JSON_ARRAY() WHERE authorities_json IS NULL; --- 9000000001 是本地平台自定义管理员账号;权限来源以 IAM 当前角色授权为准。 +-- 从 IAM 当前平台成员重建 USER 与 AUTHORIZATION 投影,避免把修复绑定到某个环境账号 ID。 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', '9000000001', 0, NULL, - COALESCE((SELECT MAX(permission_version) - FROM baiyue_iam.iam_security_subject_version - WHERE auth_user_id = 9000000001), 0), - (SELECT JSON_ARRAYAGG(role_code) - FROM ( - 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')), + 'USER', CAST(u.auth_user_id AS CHAR), 0, + IF(u.status = 'ACTIVE' AND m.status = 'ACTIVE', 'ACTIVE', 'DISABLED'), + COALESCE(v.permission_version, 0), JSON_ARRAY(), JSON_ARRAY(), + COALESCE(v.permission_version, 0), + SHA2(CONCAT('environment-repair:', u.auth_user_id, ':user:', COALESCE(v.permission_version, 0)), 256), + CONCAT('repair-', u.auth_user_id, '-u-', 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.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 tenant_id = VALUES(tenant_id), permission_version = VALUES(permission_version), @@ -104,19 +137,10 @@ ON DUPLICATE KEY UPDATE changed_at = VALUES(changed_at), 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, JSON_LENGTH(roles_json) AS role_count, JSON_LENGTH(authorities_json) AS authority_count, updated_at 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; diff --git a/scripts/security-projection-repair-contract.test.mjs b/scripts/security-projection-repair-contract.test.mjs new file mode 100644 index 000000000..be5ebcec7 --- /dev/null +++ b/scripts/security-projection-repair-contract.test.mjs @@ -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]-'/) +}) diff --git a/warehouse-overseas-service/src/main/resources/application.yml b/warehouse-overseas-service/src/main/resources/application.yml index bbc38eb5f..2f2a1d1b6 100644 --- a/warehouse-overseas-service/src/main/resources/application.yml +++ b/warehouse-overseas-service/src/main/resources/application.yml @@ -1,5 +1,6 @@ server: port: ${WAREHOUSE_PORT:9305} + max-http-request-header-size: ${SERVER_MAX_HTTP_REQUEST_HEADER_SIZE:32KB} spring: sql: init: