feat(identity): 接入认证中心多租户登录

支持 Manifest V2 动态 tid 验证、Tenant Context 同步和租户内 JIT 投影,并保留 Manifest V1 与旧 Session 兼容。\n\n增加 tenantHint、租户切换、普通注册关闭及 application/principal/tenant 两级 SSF 撤销;迁移、定向安全测试和本地双租户跨仓 E2E 已通过。\n\nrelease_required=true;未执行 Release、Staging 或真实链路。
This commit is contained in:
2026-07-28 17:28:35 +08:00
parent 0b02e62c72
commit 5c679ff13f
45 changed files with 2986 additions and 139 deletions
@@ -176,3 +176,53 @@ func TestNewDraftAllowsLoopbackHTTPOnlyInLocalEnvironments(t *testing.T) {
}
}
}
func TestApplyManifestV2RemovesFixedTenantMappingWhileV1StillRequiresIt(t *testing.T) {
draft, err := NewDraft(PairingInput{
AuthCenterURL: "https://auth.example.com", PublicBaseURL: "https://api.example.com",
WebBaseURL: "https://gateway.example.com",
}, "production")
if err != nil {
t.Fatalf("multi-tenant pairing draft rejected: %v", err)
}
applied, err := ApplyManifest(draft, ManifestApplication{
AppEnv: "production", MachineCredentialRef: "identity-machine-example",
SessionEncryptionKeyRef: "identity-session-example",
Manifest: ManifestV2{
SchemaVersion: 2, TenantMode: "multi_tenant", Issuer: "https://auth.example.com/issuer/shared",
ApplicationID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
Audience: "urn:easyai:resource:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
Capabilities: []string{"oidc_login", "api_access", "machine_to_machine", "token_introspection", "session_revocation"},
Scopes: []string{"openid", "gateway.access"},
Clients: ManifestClients{
BrowserLogin: &ManifestClient{ClientID: "browser"}, MachineToMachine: &ManifestClient{ClientID: "service"},
},
TenantContext: &ManifestTenantContext{
Endpoint: "https://auth.example.com/api/v1/runtime/tenants/{tenantId}",
Audience: "urn:easyai:auth-center:tenant-context", Scope: "tenant.context.read",
},
SecurityEvents: &ManifestSecurityEvents{
TransmitterIssuer: "https://auth.example.com/ssf",
ConfigurationEndpoint: "https://auth.example.com/.well-known/ssf-configuration/ssf",
Audience: "urn:easyai:ssf:receiver:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
},
},
})
if err != nil {
t.Fatal(err)
}
if applied.SchemaVersion != 2 || applied.TenantMode != "multi_tenant" || applied.TenantID != "" ||
applied.LocalTenantKey != "" || applied.TenantContextEndpoint == "" {
t.Fatalf("unexpected multi-tenant revision: %#v", applied)
}
v1Draft := draft
v1Draft.ID = "v1-draft"
if _, err := ApplyManifest(v1Draft, ManifestApplication{AppEnv: "production", Manifest: ManifestV1{
SchemaVersion: 1, Issuer: "https://auth.example.com/issuer/shared",
TenantID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", ApplicationID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
Capabilities: []string{}, Scopes: []string{}, Clients: ManifestClients{},
}}); err == nil {
t.Fatal("Manifest V1 without a fixed local tenant mapping was accepted")
}
}