fix(oidc): 修复登录会话落库兼容问题
部分已配对环境在 0090 首次执行后缺少 oidc_client_id,导致统一认证完成投影后无法写入 Gateway 服务端会话。新增幂等前向迁移补齐该列,并为 Token 处理及会话创建增加不泄露凭据的稳定失败分类和关联诊断。\n\n验证:OIDC Session 全量单测、HTTP 回调定向测试、迁移升级集成测试、隔离 PostgreSQL 跨仓库 OIDC E2E 和本地 Chrome 真实登录均通过。
This commit is contained in:
@@ -118,8 +118,8 @@ func (s *Server) completeOIDCLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
tokens, err := runtime.PublicClient.ExchangeCode(r.Context(), r.URL.Query().Get("code"), transaction.PKCEVerifier)
|
||||
if err != nil || tokens.AccessToken == "" || tokens.RefreshToken == "" || tokens.IDToken == "" {
|
||||
s.writeOIDCTokenFailure(w, r, "TOKEN_ENDPOINT_REJECTED", "TOKEN_RESPONSE_INVALID", "认证中心登录结果无效,请重新登录")
|
||||
if stage, category, failed := classifyOIDCTokenExchangeFailure(tokens, err); failed {
|
||||
s.writeOIDCTokenFailure(w, r, stage, category, "认证中心登录结果无效,请重新登录")
|
||||
return
|
||||
}
|
||||
identity, err := runtime.Verifier.Verify(r.Context(), tokens.AccessToken)
|
||||
@@ -145,7 +145,7 @@ func (s *Server) completeOIDCLogin(w http.ResponseWriter, r *http.Request) {
|
||||
AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, IDToken: tokens.IDToken,
|
||||
}, projection.User)
|
||||
if err != nil {
|
||||
s.writeOIDCCallbackError(w, r, http.StatusServiceUnavailable, "登录会话保存失败,请稍后重试", errorCodeOIDCSessionStoreUnavailable)
|
||||
s.writeOIDCSessionFailure(w, r, err)
|
||||
return
|
||||
}
|
||||
if err := s.replaceOIDCBrowserSession(r.Context(), r, runtime, rawSession); err != nil {
|
||||
@@ -312,11 +312,47 @@ func (s *Server) writeOIDCTokenFailure(w http.ResponseWriter, r *http.Request, s
|
||||
http.StatusUnauthorized,
|
||||
message,
|
||||
errorCodeOIDCTokenExchangeFailed,
|
||||
"",
|
||||
validationCategory,
|
||||
diagnosticID,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) writeOIDCSessionFailure(w http.ResponseWriter, r *http.Request, err error) {
|
||||
category := oidcsession.SessionCreationCategory(err)
|
||||
diagnosticID := newOIDCDiagnosticID()
|
||||
if s.logger != nil {
|
||||
s.logger.WarnContext(r.Context(), "OIDC session creation rejected",
|
||||
"event", "oidc_session_creation_rejected",
|
||||
"category", category,
|
||||
"diagnosticId", diagnosticID,
|
||||
)
|
||||
}
|
||||
s.writeOIDCCallbackErrorWithDiagnostics(
|
||||
w,
|
||||
r,
|
||||
http.StatusServiceUnavailable,
|
||||
"登录会话保存失败,请稍后重试",
|
||||
errorCodeOIDCSessionStoreUnavailable,
|
||||
category,
|
||||
diagnosticID,
|
||||
)
|
||||
}
|
||||
|
||||
func classifyOIDCTokenExchangeFailure(tokens auth.OIDCTokenResponse, err error) (stage, category string, failed bool) {
|
||||
switch {
|
||||
case err != nil:
|
||||
return "TOKEN_ENDPOINT_REJECTED", "TOKEN_ENDPOINT_REQUEST_FAILED", true
|
||||
case strings.TrimSpace(tokens.AccessToken) == "":
|
||||
return "TOKEN_RESPONSE_INVALID", "ACCESS_TOKEN_MISSING", true
|
||||
case strings.TrimSpace(tokens.RefreshToken) == "":
|
||||
return "TOKEN_RESPONSE_INVALID", "REFRESH_TOKEN_MISSING", true
|
||||
case strings.TrimSpace(tokens.IDToken) == "":
|
||||
return "TOKEN_RESPONSE_INVALID", "ID_TOKEN_MISSING", true
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) writeOIDCLoginTransactionError(w http.ResponseWriter, r *http.Request, message, reason string) {
|
||||
diagnosticID := newOIDCDiagnosticID()
|
||||
if s.logger != nil {
|
||||
|
||||
Reference in New Issue
Block a user