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:
@@ -21,10 +21,11 @@ type IdentityManagementRequest struct {
|
||||
}
|
||||
|
||||
const identityRevisionColumns = `
|
||||
id::text,state,schema_version,auth_center_url,COALESCE(issuer,''),COALESCE(tenant_id,''),COALESCE(application_id,''),
|
||||
id::text,state,schema_version,tenant_mode,auth_center_url,COALESCE(issuer,''),COALESCE(tenant_id,''),COALESCE(application_id,''),
|
||||
COALESCE(audience,''),COALESCE(browser_client_id,''),COALESCE(machine_client_id,''),scopes,capabilities,role_prefix,
|
||||
local_tenant_key,public_base_url,web_base_url,jit_enabled,legacy_jwt_enabled,token_introspection,session_revocation,
|
||||
COALESCE(security_event_issuer,''),COALESCE(security_event_configuration_url,''),COALESCE(security_event_audience,''),
|
||||
COALESCE(tenant_context_endpoint,''),COALESCE(tenant_context_audience,''),COALESCE(tenant_context_scope,''),
|
||||
COALESCE(machine_credential_ref,''),COALESCE(session_encryption_key_ref,''),session_idle_seconds,session_absolute_seconds,
|
||||
session_refresh_seconds,version,COALESCE(last_error_category,''),COALESCE(last_trace_id,''),COALESCE(last_audit_id,''),
|
||||
validated_at,activated_at,superseded_at,created_at,updated_at`
|
||||
@@ -83,11 +84,11 @@ func (s *Store) CreateIdentityConfigurationRevision(ctx context.Context, revisio
|
||||
capabilities, _ := json.Marshal(revision.Capabilities)
|
||||
return scanIdentityRevision(s.pool.QueryRow(ctx, `
|
||||
INSERT INTO gateway_identity_configuration_revisions (
|
||||
id,state,schema_version,auth_center_url,role_prefix,local_tenant_key,public_base_url,web_base_url,
|
||||
id,state,schema_version,tenant_mode,auth_center_url,role_prefix,local_tenant_key,public_base_url,web_base_url,
|
||||
jit_enabled,legacy_jwt_enabled,scopes,capabilities,session_idle_seconds,session_absolute_seconds,session_refresh_seconds
|
||||
) VALUES ($1::uuid,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12::jsonb,$13,$14,$15)
|
||||
) VALUES ($1::uuid,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12::jsonb,$13::jsonb,$14,$15,$16)
|
||||
RETURNING `+identityRevisionColumns,
|
||||
revision.ID, revision.State, revision.SchemaVersion, revision.AuthCenterURL, revision.RolePrefix,
|
||||
revision.ID, revision.State, revision.SchemaVersion, revision.TenantMode, revision.AuthCenterURL, revision.RolePrefix,
|
||||
revision.LocalTenantKey, revision.PublicBaseURL, revision.WebBaseURL, revision.JITEnabled, revision.LegacyJWTEnabled,
|
||||
string(scopes), string(capabilities), revision.SessionIdleSeconds, revision.SessionAbsoluteSeconds, revision.SessionRefreshSeconds,
|
||||
))
|
||||
@@ -198,16 +199,19 @@ FROM gateway_identity_configuration_revisions WHERE id=$1::uuid FOR UPDATE`, id)
|
||||
capabilities, _ := json.Marshal(updated.Capabilities)
|
||||
revision, err := scanIdentityRevision(tx.QueryRow(ctx, `
|
||||
UPDATE gateway_identity_configuration_revisions SET
|
||||
issuer=$3,tenant_id=$4,application_id=$5,audience=NULLIF($6,''),browser_client_id=NULLIF($7,''),machine_client_id=NULLIF($8,''),
|
||||
scopes=$9::jsonb,capabilities=$10::jsonb,token_introspection=$11,session_revocation=$12,
|
||||
security_event_issuer=NULLIF($13,''),security_event_configuration_url=NULLIF($14,''),security_event_audience=NULLIF($15,''),
|
||||
machine_credential_ref=NULLIF($16,''),session_encryption_key_ref=NULLIF($17,''),last_trace_id=NULLIF($18,''),
|
||||
last_audit_id=NULLIF($19,''),last_error_category=NULL,version=version+1,updated_at=now()
|
||||
schema_version=$3,tenant_mode=$4,issuer=$5,tenant_id=NULLIF($6,''),application_id=$7,audience=NULLIF($8,''),
|
||||
browser_client_id=NULLIF($9,''),machine_client_id=NULLIF($10,''),local_tenant_key=$11,
|
||||
scopes=$12::jsonb,capabilities=$13::jsonb,token_introspection=$14,session_revocation=$15,
|
||||
security_event_issuer=NULLIF($16,''),security_event_configuration_url=NULLIF($17,''),security_event_audience=NULLIF($18,''),
|
||||
tenant_context_endpoint=NULLIF($19,''),tenant_context_audience=NULLIF($20,''),tenant_context_scope=NULLIF($21,''),
|
||||
machine_credential_ref=NULLIF($22,''),session_encryption_key_ref=NULLIF($23,''),last_trace_id=NULLIF($24,''),
|
||||
last_audit_id=NULLIF($25,''),last_error_category=NULL,version=version+1,updated_at=now()
|
||||
WHERE id=$1::uuid AND version=$2 AND state='draft'
|
||||
RETURNING `+identityRevisionColumns,
|
||||
id, expectedVersion, updated.Issuer, updated.TenantID, updated.ApplicationID, updated.Audience,
|
||||
updated.BrowserClientID, updated.MachineClientID, string(scopes), string(capabilities), updated.TokenIntrospection,
|
||||
id, expectedVersion, updated.SchemaVersion, updated.TenantMode, updated.Issuer, updated.TenantID, updated.ApplicationID, updated.Audience,
|
||||
updated.BrowserClientID, updated.MachineClientID, updated.LocalTenantKey, string(scopes), string(capabilities), updated.TokenIntrospection,
|
||||
updated.SessionRevocation, updated.SecurityEventIssuer, updated.SecurityEventConfigURL, updated.SecurityEventAudience,
|
||||
updated.TenantContextEndpoint, updated.TenantContextAudience, updated.TenantContextScope,
|
||||
updated.MachineCredentialRef, updated.SessionEncryptionKeyRef, updated.LastTraceID, updated.LastAuditID,
|
||||
))
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
@@ -296,8 +300,12 @@ func (s *Store) ActivateIdentityRevision(ctx context.Context, id string, expecte
|
||||
return identity.Revision{}, false, identity.ErrBreakGlassRequired
|
||||
}
|
||||
var tenantExists bool
|
||||
if err := tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM gateway_tenants WHERE tenant_key=(
|
||||
SELECT local_tenant_key FROM gateway_identity_configuration_revisions WHERE id=$1::uuid) AND status='active')`, id).Scan(&tenantExists); err != nil {
|
||||
if err := tx.QueryRow(ctx, `SELECT revision.tenant_mode='multi_tenant' OR EXISTS(
|
||||
SELECT 1 FROM gateway_tenants tenant
|
||||
WHERE tenant.tenant_key=revision.local_tenant_key AND tenant.status='active'
|
||||
)
|
||||
FROM gateway_identity_configuration_revisions revision
|
||||
WHERE revision.id=$1::uuid`, id).Scan(&tenantExists); err != nil {
|
||||
return identity.Revision{}, false, err
|
||||
}
|
||||
if !tenantExists {
|
||||
@@ -428,11 +436,12 @@ func scanIdentityRevision(row scanner) (identity.Revision, error) {
|
||||
var state string
|
||||
var scopes, capabilities []byte
|
||||
if err := row.Scan(
|
||||
&revision.ID, &state, &revision.SchemaVersion, &revision.AuthCenterURL, &revision.Issuer, &revision.TenantID,
|
||||
&revision.ID, &state, &revision.SchemaVersion, &revision.TenantMode, &revision.AuthCenterURL, &revision.Issuer, &revision.TenantID,
|
||||
&revision.ApplicationID, &revision.Audience, &revision.BrowserClientID, &revision.MachineClientID, &scopes,
|
||||
&capabilities, &revision.RolePrefix, &revision.LocalTenantKey, &revision.PublicBaseURL, &revision.WebBaseURL,
|
||||
&revision.JITEnabled, &revision.LegacyJWTEnabled, &revision.TokenIntrospection, &revision.SessionRevocation,
|
||||
&revision.SecurityEventIssuer, &revision.SecurityEventConfigURL, &revision.SecurityEventAudience,
|
||||
&revision.TenantContextEndpoint, &revision.TenantContextAudience, &revision.TenantContextScope,
|
||||
&revision.MachineCredentialRef, &revision.SessionEncryptionKeyRef, &revision.SessionIdleSeconds,
|
||||
&revision.SessionAbsoluteSeconds, &revision.SessionRefreshSeconds, &revision.Version, &revision.LastErrorCategory,
|
||||
&revision.LastTraceID, &revision.LastAuditID, &revision.ValidatedAt, &revision.ActivatedAt, &revision.SupersededAt,
|
||||
|
||||
Reference in New Issue
Block a user