feat(identity): 实现统一认证运行时热切换
从 Active Revision 构造并验证 OIDC、BFF Session、Introspection 与 SSF Runtime,在数据库激活成功后原子替换内存引用。请求链路使用不可变快照,失败保留当前运行时,本地管理登录不受影响。\n\n验证:go test ./apps/api/...;go vet ./apps/api/...
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/oidcsession"
|
||||
ssfreceiver "github.com/easyai/easyai-ai-gateway/apps/api/internal/securityevents"
|
||||
)
|
||||
|
||||
type oidcTokenVerifier interface {
|
||||
Verify(context.Context, string) (*auth.User, error)
|
||||
}
|
||||
|
||||
// identityRequestRuntime is an immutable request-level snapshot. A handler that
|
||||
// starts with one runtime keeps using it even when an administrator activates a
|
||||
// new revision while that request is in flight.
|
||||
type identityRequestRuntime struct {
|
||||
Revision identity.Revision
|
||||
Verifier oidcTokenVerifier
|
||||
PublicClient oidcPublicClient
|
||||
Sessions oidcSessionManager
|
||||
SessionCipher *oidcsession.Cipher
|
||||
SecurityEvents *ssfreceiver.ConnectionManager
|
||||
CookieSecure bool
|
||||
BrowserEnabled bool
|
||||
}
|
||||
|
||||
func (s *Server) currentIdentityRuntime() *identityRequestRuntime {
|
||||
if s.identityRuntime != nil {
|
||||
runtime := s.identityRuntime.Current()
|
||||
if runtime == nil {
|
||||
return nil
|
||||
}
|
||||
return &identityRequestRuntime{
|
||||
Revision: runtime.Revision, Verifier: runtime.Verifier, PublicClient: runtime.PublicClient,
|
||||
Sessions: runtime.Sessions, SessionCipher: runtime.SessionCipher, SecurityEvents: runtime.SecurityEvents,
|
||||
CookieSecure: runtime.CookieSecure, BrowserEnabled: runtime.PublicClient != nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Compatibility path for focused HTTP tests. NewServer never uses these
|
||||
// static fields after identity revisions are enabled.
|
||||
if !s.cfg.OIDCEnabled && s.cfg.OIDCIssuer == "" && s.oidcClient == nil && s.oidcSessions == nil && s.oidcSessionCipher == nil && s.securityEventManager == nil {
|
||||
return nil
|
||||
}
|
||||
webBaseURL := s.cfg.WebBaseURL
|
||||
if webBaseURL == "" {
|
||||
webBaseURL = s.cfg.CORSAllowedOrigin
|
||||
}
|
||||
var verifier oidcTokenVerifier
|
||||
if s.auth != nil {
|
||||
verifier = s.auth.OIDCVerifier
|
||||
}
|
||||
return &identityRequestRuntime{
|
||||
Revision: identity.Revision{
|
||||
State: identity.RevisionActive, Issuer: s.cfg.OIDCIssuer, LocalTenantKey: s.cfg.OIDCGatewayTenantKey,
|
||||
WebBaseURL: webBaseURL, PublicBaseURL: s.cfg.PublicBaseURL,
|
||||
JITEnabled: s.cfg.OIDCJITProvisioningEnabled, LegacyJWTEnabled: s.cfg.OIDCAcceptLegacyHS256,
|
||||
SessionAbsoluteSeconds: s.cfg.OIDCSessionAbsoluteTTLSeconds,
|
||||
},
|
||||
Verifier: verifier, PublicClient: s.oidcClient, Sessions: s.oidcSessions,
|
||||
SessionCipher: s.oidcSessionCipher, SecurityEvents: s.securityEventManager,
|
||||
CookieSecure: s.cfg.OIDCSessionCookieSecure,
|
||||
BrowserEnabled: s.cfg.OIDCEnabled && s.cfg.OIDCBrowserSessionEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) currentSecurityEventManager() *ssfreceiver.ConnectionManager {
|
||||
if runtime := s.currentIdentityRuntime(); runtime != nil {
|
||||
return runtime.SecurityEvents
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) oidcCookieSecure() bool {
|
||||
if runtime := s.currentIdentityRuntime(); runtime != nil {
|
||||
return runtime.CookieSecure
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func originMatchesBaseURL(origin, baseURL string) bool {
|
||||
originURL, err := url.Parse(strings.TrimSpace(origin))
|
||||
if err != nil || originURL.User != nil || originURL.Path != "" || originURL.RawQuery != "" || originURL.Fragment != "" {
|
||||
return false
|
||||
}
|
||||
base, err := url.Parse(strings.TrimSpace(baseURL))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(originURL.Scheme, base.Scheme) && strings.EqualFold(originURL.Host, base.Host)
|
||||
}
|
||||
Reference in New Issue
Block a user