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:
@@ -22,6 +22,7 @@ var (
|
||||
)
|
||||
|
||||
type RevisionPolicy struct {
|
||||
TenantMode string `json:"tenantMode"`
|
||||
LocalTenantKey string `json:"localTenantKey"`
|
||||
RolePrefix string `json:"rolePrefix"`
|
||||
JITEnabled bool `json:"jitEnabled"`
|
||||
@@ -32,7 +33,8 @@ type RevisionPolicy struct {
|
||||
}
|
||||
|
||||
func (policy RevisionPolicy) Validate() error {
|
||||
if strings.TrimSpace(policy.LocalTenantKey) == "" || strings.TrimSpace(policy.RolePrefix) == "" {
|
||||
if strings.TrimSpace(policy.RolePrefix) == "" ||
|
||||
policy.TenantMode != "multi_tenant" && strings.TrimSpace(policy.LocalTenantKey) == "" {
|
||||
return errors.New("local tenant mapping and role prefix are required")
|
||||
}
|
||||
if policy.SessionIdleSeconds <= 0 || policy.SessionAbsoluteSeconds <= policy.SessionIdleSeconds ||
|
||||
@@ -56,6 +58,7 @@ type Revision struct {
|
||||
ID string `json:"id"`
|
||||
State RevisionState `json:"state"`
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
TenantMode string `json:"tenantMode,omitempty"`
|
||||
AuthCenterURL string `json:"authCenterUrl"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
@@ -76,6 +79,9 @@ type Revision struct {
|
||||
SecurityEventIssuer string `json:"securityEventIssuer,omitempty"`
|
||||
SecurityEventConfigURL string `json:"securityEventConfigurationUrl,omitempty"`
|
||||
SecurityEventAudience string `json:"securityEventAudience,omitempty"`
|
||||
TenantContextEndpoint string `json:"tenantContextEndpoint,omitempty"`
|
||||
TenantContextAudience string `json:"tenantContextAudience,omitempty"`
|
||||
TenantContextScope string `json:"tenantContextScope,omitempty"`
|
||||
MachineCredentialRef string `json:"-"`
|
||||
SessionEncryptionKeyRef string `json:"-"`
|
||||
SessionIdleSeconds int `json:"sessionIdleSeconds"`
|
||||
@@ -126,7 +132,7 @@ func NewDraft(input PairingInput, appEnv string) (Revision, error) {
|
||||
publicBase, _ := exactBaseURL(input.PublicBaseURL, appEnv)
|
||||
webBase, _ := exactBaseURL(input.WebBaseURL, appEnv)
|
||||
return Revision{
|
||||
ID: uuid.NewString(), State: RevisionDraft, SchemaVersion: 1,
|
||||
ID: uuid.NewString(), State: RevisionDraft, SchemaVersion: 1, TenantMode: "single_tenant",
|
||||
AuthCenterURL: authCenter, RolePrefix: "gateway.", LocalTenantKey: strings.TrimSpace(input.LocalTenantKey),
|
||||
PublicBaseURL: publicBase, WebBaseURL: webBase, JITEnabled: true, LegacyJWTEnabled: input.LegacyJWTEnabled,
|
||||
Scopes: []string{}, Capabilities: []string{}, SessionIdleSeconds: 1800, SessionAbsoluteSeconds: 28800,
|
||||
@@ -151,8 +157,13 @@ func ApplyManifest(revision Revision, input ManifestApplication) (Revision, erro
|
||||
if capabilities["oidc_login"] && strings.TrimSpace(input.SessionEncryptionKeyRef) == "" {
|
||||
return Revision{}, errors.New("session encryption key reference is required")
|
||||
}
|
||||
if input.Manifest.SchemaVersion == 1 && strings.TrimSpace(revision.LocalTenantKey) == "" {
|
||||
return Revision{}, ErrLocalTenantInvalid
|
||||
}
|
||||
revision.SchemaVersion = input.Manifest.SchemaVersion
|
||||
revision.Issuer = strings.TrimRight(input.Manifest.Issuer, "/")
|
||||
revision.TenantID = input.Manifest.TenantID
|
||||
revision.TenantMode = input.Manifest.TenantMode
|
||||
revision.ApplicationID = input.Manifest.ApplicationID
|
||||
revision.Audience = input.Manifest.Audience
|
||||
revision.Scopes = append([]string(nil), input.Manifest.Scopes...)
|
||||
@@ -165,6 +176,16 @@ func ApplyManifest(revision Revision, input ManifestApplication) (Revision, erro
|
||||
}
|
||||
revision.TokenIntrospection = capabilities["token_introspection"]
|
||||
revision.SessionRevocation = capabilities["session_revocation"]
|
||||
if input.Manifest.SchemaVersion == 2 {
|
||||
revision.LocalTenantKey = ""
|
||||
revision.TenantContextEndpoint = input.Manifest.TenantContext.Endpoint
|
||||
revision.TenantContextAudience = input.Manifest.TenantContext.Audience
|
||||
revision.TenantContextScope = input.Manifest.TenantContext.Scope
|
||||
} else {
|
||||
revision.TenantContextEndpoint = ""
|
||||
revision.TenantContextAudience = ""
|
||||
revision.TenantContextScope = ""
|
||||
}
|
||||
if input.Manifest.SecurityEvents != nil {
|
||||
revision.SecurityEventIssuer = input.Manifest.SecurityEvents.TransmitterIssuer
|
||||
revision.SecurityEventConfigURL = input.Manifest.SecurityEvents.ConfigurationEndpoint
|
||||
@@ -209,9 +230,6 @@ func (input PairingInput) ConsumerMetadata(sessionRevocation bool, appEnv string
|
||||
if !sameSiteBaseURLs(publicBase, webBase) {
|
||||
return ConsumerMetadata{}, errors.New("public base URL and web base URL must be same-site")
|
||||
}
|
||||
if strings.TrimSpace(input.LocalTenantKey) == "" {
|
||||
return ConsumerMetadata{}, errors.New("local tenant mapping is required")
|
||||
}
|
||||
metadata := ConsumerMetadata{
|
||||
PublicBaseURL: publicBase, WebBaseURL: webBase,
|
||||
RedirectURIs: []string{publicBase + "/api/v1/auth/oidc/callback"},
|
||||
|
||||
Reference in New Issue
Block a user