feat: 接入 SSF 实时会话撤销
This commit is contained in:
@@ -13,21 +13,23 @@ import (
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/oidcsession"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/runner"
|
||||
ssfreceiver "github.com/easyai/easyai-ai-gateway/apps/api/internal/securityevents"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ctx context.Context
|
||||
cfg config.Config
|
||||
store *store.Store
|
||||
oidcUserResolver oidcUserResolver
|
||||
auth *auth.Authenticator
|
||||
oidcClient oidcPublicClient
|
||||
oidcSessions oidcSessionManager
|
||||
oidcSessionCipher *oidcsession.Cipher
|
||||
runner *runner.Service
|
||||
logger *slog.Logger
|
||||
geminiUploadSessions sync.Map
|
||||
ctx context.Context
|
||||
cfg config.Config
|
||||
store *store.Store
|
||||
oidcUserResolver oidcUserResolver
|
||||
auth *auth.Authenticator
|
||||
oidcClient oidcPublicClient
|
||||
oidcSessions oidcSessionManager
|
||||
oidcSessionCipher *oidcsession.Cipher
|
||||
runner *runner.Service
|
||||
logger *slog.Logger
|
||||
geminiUploadSessions sync.Map
|
||||
securityEventReceiver http.Handler
|
||||
}
|
||||
|
||||
type oidcPublicClient interface {
|
||||
@@ -63,14 +65,58 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
server.auth.LegacyJWTEnabled = !cfg.OIDCEnabled || cfg.OIDCAcceptLegacyHS256
|
||||
server.auth.ServerMainInternalKey = cfg.ServerMainInternalKey
|
||||
server.auth.ServerMainInternalSecret = cfg.ServerMainInternalSecret
|
||||
securityEventMetrics := &ssfreceiver.Metrics{}
|
||||
var securityEventService *ssfreceiver.Service
|
||||
if cfg.OIDCSecurityEventsEnabled {
|
||||
ssfVerifier, err := ssfreceiver.NewVerifier(ssfreceiver.VerifierConfig{
|
||||
TransmitterIssuer: cfg.OIDCSecurityEventsTransmitterIssuer,
|
||||
Audience: cfg.OIDCSecurityEventsReceiverAudience, SubjectIssuer: cfg.OIDCIssuer,
|
||||
TenantID: cfg.OIDCTenantID, StreamID: cfg.OIDCSecurityEventsStreamID,
|
||||
ClockSkew: time.Duration(cfg.OIDCSecurityEventsClockSkewSeconds) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
panic("invalid OIDC security event verifier configuration: " + err.Error())
|
||||
}
|
||||
ssfVerifier.SetMetrics(securityEventMetrics)
|
||||
securityEventService, err = ssfreceiver.NewService(db, ssfreceiver.ServiceConfig{
|
||||
Issuer: cfg.OIDCSecurityEventsTransmitterIssuer, Audience: cfg.OIDCSecurityEventsReceiverAudience,
|
||||
StreamID: cfg.OIDCSecurityEventsStreamID, ManagementTokenURL: cfg.OIDCSecurityEventsManagementTokenURL,
|
||||
ManagementClientID: cfg.OIDCSecurityEventsManagementClientID, ManagementSecret: cfg.OIDCSecurityEventsManagementClientSecret,
|
||||
HeartbeatInterval: time.Duration(cfg.OIDCSecurityEventsHeartbeatIntervalSeconds) * time.Second,
|
||||
StaleAfter: time.Duration(cfg.OIDCSecurityEventsStaleAfterSeconds) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
panic("invalid OIDC security event heartbeat configuration: " + err.Error())
|
||||
}
|
||||
securityEventService.SetMetrics(securityEventMetrics)
|
||||
if err := securityEventService.Start(ctx); err != nil {
|
||||
panic("OIDC security event state initialization failed")
|
||||
}
|
||||
receiver, err := ssfreceiver.NewHandler(
|
||||
ssfVerifier, db, cfg.OIDCSecurityEventsTransmitterIssuer, cfg.OIDCSecurityEventsReceiverAudience,
|
||||
cfg.OIDCSecurityEventsStreamID, cfg.OIDCSecurityEventsBearerSecret, cfg.OIDCSecurityEventsBearerSecretNext,
|
||||
)
|
||||
if err != nil {
|
||||
panic("invalid OIDC security event receiver configuration: " + err.Error())
|
||||
}
|
||||
receiver.SetMetrics(securityEventMetrics)
|
||||
server.securityEventReceiver = receiver
|
||||
}
|
||||
if cfg.OIDCEnabled {
|
||||
var evaluator func(context.Context, auth.OIDCSecurityEventIdentity) (auth.OIDCSecurityEventEvaluation, error)
|
||||
if securityEventService != nil {
|
||||
evaluator = securityEventService.Evaluate
|
||||
}
|
||||
verifier, err := auth.NewOIDCVerifier(auth.OIDCConfig{
|
||||
Issuer: cfg.OIDCIssuer, Audience: cfg.OIDCAudience, TenantID: cfg.OIDCTenantID,
|
||||
RolePrefix: cfg.OIDCRolePrefix, RequiredScopes: cfg.OIDCRequiredScopes,
|
||||
JWKSCacheTTL: time.Duration(cfg.OIDCJWKSCacheTTLSeconds) * time.Second,
|
||||
IntrospectionEnabled: cfg.OIDCIntrospectionEnabled,
|
||||
IntrospectionClientID: cfg.OIDCIntrospectionClientID,
|
||||
IntrospectionClientSecret: cfg.OIDCIntrospectionClientSecret,
|
||||
JWKSCacheTTL: time.Duration(cfg.OIDCJWKSCacheTTLSeconds) * time.Second,
|
||||
IntrospectionEnabled: cfg.OIDCIntrospectionEnabled,
|
||||
IntrospectionClientID: cfg.OIDCIntrospectionClientID,
|
||||
IntrospectionClientSecret: cfg.OIDCIntrospectionClientSecret,
|
||||
SecurityEventEvaluator: evaluator,
|
||||
IntrospectionObserver: securityEventMetrics.ObserveIntrospection,
|
||||
JWKSRefreshFailureObserver: func() { securityEventMetrics.ObserveJWKSRefreshFailure("oidc") },
|
||||
})
|
||||
if err != nil {
|
||||
panic("invalid OIDC configuration: " + err.Error())
|
||||
@@ -118,6 +164,7 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", server.health)
|
||||
mux.HandleFunc("GET /readyz", server.ready)
|
||||
mux.Handle("GET /metrics", securityEventMetrics.Handler(db, cfg.OIDCSecurityEventsTransmitterIssuer, cfg.OIDCSecurityEventsReceiverAudience, cfg.OIDCSecurityEventsEnabled))
|
||||
mux.HandleFunc("GET /static/simulation/{asset}", serveSimulationAsset)
|
||||
mux.HandleFunc("GET /static/generated/{asset}", server.serveGeneratedStaticAsset)
|
||||
mux.HandleFunc("GET /static/uploaded/{asset}", server.serveUploadedStaticAsset)
|
||||
@@ -128,6 +175,9 @@ func NewServerWithContext(ctx context.Context, cfg config.Config, db *store.Stor
|
||||
mux.HandleFunc("GET /api/v1/auth/oidc/callback", server.completeOIDCLogin)
|
||||
mux.HandleFunc("POST /api/v1/auth/oidc/logout", server.logoutOIDCSession)
|
||||
mux.HandleFunc("DELETE /api/v1/auth/oidc/session", server.deleteOIDCBrowserSession)
|
||||
if server.securityEventReceiver != nil {
|
||||
mux.HandleFunc("POST /api/v1/security-events/ssf", server.receiveSecurityEvent)
|
||||
}
|
||||
mux.Handle("GET /api/v1/me", server.requireUser(auth.PermissionBasic, http.HandlerFunc(server.me)))
|
||||
mux.Handle("GET /api/v1/public/catalog/providers", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listCatalogProviders)))
|
||||
mux.Handle("GET /api/v1/public/catalog/base-models", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.listBaseModels)))
|
||||
@@ -284,6 +334,8 @@ func oidcSessionRequestError(err error) error {
|
||||
return auth.NewRequestAuthError(http.StatusServiceUnavailable, "OIDC_SESSION_REFRESH_UNAVAILABLE", "认证中心暂时不可用,请稍后重试")
|
||||
case errors.Is(err, oidcsession.ErrSessionStoreUnavailable):
|
||||
return auth.NewRequestAuthError(http.StatusServiceUnavailable, "OIDC_SESSION_STORE_UNAVAILABLE", "登录会话存储暂时不可用")
|
||||
case errors.Is(err, oidcsession.ErrSecurityStateUnavailable):
|
||||
return auth.NewRequestAuthError(http.StatusServiceUnavailable, "OIDC_SECURITY_EVENT_STATE_UNAVAILABLE", "认证撤销状态暂时不可用")
|
||||
case errors.Is(err, oidcsession.ErrGatewayUserDisabled):
|
||||
return auth.NewRequestAuthError(http.StatusForbidden, "GATEWAY_USER_DISABLED", "该 Gateway 账号已停用,请联系管理员")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user