feat(observability): 增加 OIDC 失败关联诊断

将 OIDC Access Token 与 ID Token 校验失败映射为固定安全分类,并生成 diagnosticId 关联浏览器错误与服务端日志。日志不记录授权码、Token 或底层敏感输入,且保留 errors.Is(ErrUnauthorized) 兼容语义。

验证:go test ./... -count=1(apps/api)
This commit is contained in:
2026-07-28 06:19:24 +08:00
parent b51ecc5eed
commit 4a464cacca
5 changed files with 180 additions and 35 deletions
+24 -3
View File
@@ -111,17 +111,17 @@ func (s *Server) completeOIDCLogin(w http.ResponseWriter, r *http.Request) {
}
tokens, err := runtime.PublicClient.ExchangeCode(r.Context(), r.URL.Query().Get("code"), transaction.PKCEVerifier)
if err != nil || tokens.AccessToken == "" || tokens.RefreshToken == "" || tokens.IDToken == "" {
s.writeOIDCCallbackError(w, r, http.StatusUnauthorized, "认证中心登录结果无效,请重新登录", errorCodeOIDCTokenExchangeFailed)
s.writeOIDCTokenFailure(w, r, "TOKEN_ENDPOINT_REJECTED", "TOKEN_RESPONSE_INVALID", "认证中心登录结果无效,请重新登录")
return
}
identity, err := runtime.Verifier.Verify(r.Context(), tokens.AccessToken)
if err != nil || identity == nil {
s.writeOIDCCallbackError(w, r, http.StatusUnauthorized, "认证中心访问令牌校验失败", errorCodeOIDCTokenExchangeFailed)
s.writeOIDCTokenFailure(w, r, "ACCESS_TOKEN_INVALID", auth.OIDCValidationCategory(err), "认证中心访问令牌校验失败")
return
}
idSubject, err := runtime.PublicClient.VerifyIDToken(r.Context(), tokens.IDToken, transaction.Nonce)
if err != nil || idSubject != identity.ID {
s.writeOIDCCallbackError(w, r, http.StatusUnauthorized, "认证中心身份令牌校验失败", errorCodeOIDCTokenExchangeFailed)
s.writeOIDCTokenFailure(w, r, "ID_TOKEN_INVALID", auth.OIDCValidationCategory(err), "认证中心身份令牌校验失败")
return
}
projection, err := s.resolveOIDCUserProjectionForRevision(r.Context(), r, identity, runtime.Revision)
@@ -253,6 +253,27 @@ func (s *Server) writeOIDCCallbackError(w http.ResponseWriter, r *http.Request,
s.writeOIDCCallbackErrorWithDiagnostics(w, r, status, message, code, "", "")
}
func (s *Server) writeOIDCTokenFailure(w http.ResponseWriter, r *http.Request, stage, validationCategory, message string) {
diagnosticID := newOIDCDiagnosticID()
if s.logger != nil {
s.logger.WarnContext(r.Context(), "OIDC token processing rejected",
"event", "oidc_token_processing_rejected",
"stage", stage,
"validationCategory", validationCategory,
"diagnosticId", diagnosticID,
)
}
s.writeOIDCCallbackErrorWithDiagnostics(
w,
r,
http.StatusUnauthorized,
message,
errorCodeOIDCTokenExchangeFailed,
"",
diagnosticID,
)
}
func (s *Server) writeOIDCLoginTransactionError(w http.ResponseWriter, r *http.Request, message, reason string) {
diagnosticID := newOIDCDiagnosticID()
if s.logger != nil {