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:
@@ -61,18 +61,31 @@ type ManifestSecurityEvents struct {
|
||||
Audience string `json:"audience"`
|
||||
}
|
||||
|
||||
type ManifestTenantContext struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Audience string `json:"audience"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type ManifestV1 struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
TenantMode string `json:"tenant_mode,omitempty"`
|
||||
Issuer string `json:"issuer"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
ApplicationID string `json:"application_id"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Audience string `json:"audience,omitempty"`
|
||||
Scopes []string `json:"scopes"`
|
||||
Clients ManifestClients `json:"clients"`
|
||||
TenantContext *ManifestTenantContext `json:"tenant_context,omitempty"`
|
||||
SecurityEvents *ManifestSecurityEvents `json:"security_events,omitempty"`
|
||||
}
|
||||
|
||||
// ManifestV2 intentionally shares the wire structure with ManifestV1 so the
|
||||
// onboarding delivery can be decoded without guessing a union variant. Validate
|
||||
// always branches on schema_version and rejects fields from the other mode.
|
||||
type ManifestV2 = ManifestV1
|
||||
|
||||
type MachineCredential struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
@@ -86,11 +99,30 @@ type CredentialDelivery struct {
|
||||
}
|
||||
|
||||
func (manifest ManifestV1) Validate(appEnv string) error {
|
||||
if manifest.SchemaVersion != 1 || validatePublicIdentityURL(manifest.Issuer, appEnv) != nil {
|
||||
if validatePublicIdentityURL(manifest.Issuer, appEnv) != nil {
|
||||
return errors.New("application manifest identity metadata is invalid")
|
||||
}
|
||||
if _, err := uuid.Parse(manifest.TenantID); err != nil {
|
||||
return errors.New("application manifest tenant is invalid")
|
||||
switch manifest.SchemaVersion {
|
||||
case 1:
|
||||
if strings.TrimSpace(manifest.TenantMode) != "" || manifest.TenantContext != nil {
|
||||
return errors.New("application manifest tenant mode is invalid")
|
||||
}
|
||||
if _, err := uuid.Parse(manifest.TenantID); err != nil {
|
||||
return errors.New("application manifest tenant is invalid")
|
||||
}
|
||||
case 2:
|
||||
if manifest.TenantMode != "multi_tenant" || strings.TrimSpace(manifest.TenantID) != "" {
|
||||
return errors.New("application manifest tenant mode is invalid")
|
||||
}
|
||||
if manifest.TenantContext == nil ||
|
||||
validatePublicIdentityURL(strings.ReplaceAll(manifest.TenantContext.Endpoint, "{tenantId}", uuid.Nil.String()), appEnv) != nil ||
|
||||
!strings.Contains(manifest.TenantContext.Endpoint, "{tenantId}") ||
|
||||
manifest.TenantContext.Audience != "urn:easyai:auth-center:tenant-context" ||
|
||||
manifest.TenantContext.Scope != "tenant.context.read" {
|
||||
return errors.New("application manifest tenant context is invalid")
|
||||
}
|
||||
default:
|
||||
return errors.New("application manifest identity metadata is invalid")
|
||||
}
|
||||
if _, err := uuid.Parse(manifest.ApplicationID); err != nil {
|
||||
return errors.New("application manifest application is invalid")
|
||||
@@ -107,6 +139,11 @@ func (manifest ManifestV1) Validate(appEnv string) error {
|
||||
capabilities["token_introspection"] && !capabilities["machine_to_machine"] {
|
||||
return errors.New("application manifest capability dependencies are invalid")
|
||||
}
|
||||
if manifest.SchemaVersion == 2 &&
|
||||
(!capabilities["oidc_login"] || !capabilities["machine_to_machine"] ||
|
||||
!capabilities["token_introspection"] || !capabilities["session_revocation"]) {
|
||||
return errors.New("application manifest multi-tenant capabilities are invalid")
|
||||
}
|
||||
if capabilities["oidc_login"] && (manifest.Clients.BrowserLogin == nil || strings.TrimSpace(manifest.Clients.BrowserLogin.ClientID) == "") {
|
||||
return errors.New("application manifest browser client is missing")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user