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
+29 -6
View File
@@ -99,14 +99,18 @@ func (builder *RuntimeBuilder) Build(ctx context.Context, revision identity.Revi
if err := identity.ValidateRevisionURLs(revision, builder.config.AppEnv); err != nil {
return nil, err
}
if builder.store == nil || builder.secrets == nil || revision.Issuer == "" || revision.TenantID == "" ||
revision.Audience == "" || revision.RolePrefix == "" {
if builder.store == nil || builder.secrets == nil || revision.Issuer == "" ||
revision.Audience == "" || revision.RolePrefix == "" ||
revision.TenantMode != "multi_tenant" && revision.TenantID == "" ||
revision.TenantMode == "multi_tenant" && (revision.ApplicationID == "" || revision.TenantContextEndpoint == "") {
return nil, errors.New("identity runtime configuration is incomplete")
}
if exists, err := builder.store.HasActiveTenantKey(ctx, revision.LocalTenantKey); err != nil {
return nil, err
} else if !exists {
return nil, identity.ErrLocalTenantInvalid
if revision.TenantMode != "multi_tenant" {
if exists, err := builder.store.HasActiveTenantKey(ctx, revision.LocalTenantKey); err != nil {
return nil, err
} else if !exists {
return nil, identity.ErrLocalTenantInvalid
}
}
runtimeCtx, cancel := context.WithCancel(builder.ctx)
runtime := &Runtime{Revision: revision, CookieSecure: secureCookieFor(revision.PublicBaseURL), close: cancel}
@@ -151,6 +155,7 @@ func (builder *RuntimeBuilder) Build(ctx context.Context, revision identity.Revi
verifier, err := auth.NewOIDCVerifier(auth.OIDCConfig{
AppEnv: builder.config.AppEnv,
Issuer: revision.Issuer, Audience: revision.Audience, TenantID: revision.TenantID,
TenantMode: revision.TenantMode, ApplicationID: revision.ApplicationID, ClientID: revision.BrowserClientID,
RolePrefix: revision.RolePrefix, RequiredScopes: append([]string(nil), revision.Scopes...),
JWKSCacheTTL: builder.config.JWKSCacheTTL, IntrospectionEnabled: revision.TokenIntrospection,
IntrospectionCredentialProvider: credentialProvider, SecurityEventEvaluator: evaluator,
@@ -166,6 +171,23 @@ func (builder *RuntimeBuilder) Build(ctx context.Context, revision identity.Revi
return nil, err
}
runtime.Verifier = verifier
if revision.TenantMode == "multi_tenant" {
tenantContext, err := identity.NewTenantContextClient(
builder.config.AppEnv, revision.Issuer, revision.ApplicationID,
revision.TenantContextEndpoint, revision.TenantContextAudience, revision.TenantContextScope,
credentialProvider, nil,
)
if err != nil {
cancel()
return nil, err
}
runtime.TenantContext = tenantContext
runtime.start = func() {
go runTenantContextSynchronizer(
runtimeCtx, builder.store, tenantContext, revision.Issuer, revision.ApplicationID,
)
}
}
if slices.Contains(revision.Capabilities, "oidc_login") {
if revision.BrowserClientID == "" || revision.SessionEncryptionKeyRef == "" {
@@ -396,6 +418,7 @@ func (builder *RuntimeBuilder) newSecurityEventManager(ctx context.Context, revi
func (builder *RuntimeBuilder) securityEventManagerConfig(revision identity.Revision, strictRevisionBinding bool) securityevents.ConnectionManagerConfig {
return securityevents.ConnectionManagerConfig{
AppEnv: builder.config.AppEnv, OIDCEnabled: true, OIDCIssuer: revision.Issuer, OIDCTenantID: revision.TenantID,
OIDCApplicationID: revision.ApplicationID, OIDCTenantMode: revision.TenantMode,
ManagementClientID: revision.MachineClientID, PublicBaseURL: revision.PublicBaseURL,
ExpectedTransmitterIssuer: strings.TrimRight(strings.TrimSpace(revision.SecurityEventIssuer), "/"),
ExpectedAudience: revision.SecurityEventAudience,