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
+7 -7
View File
@@ -121,30 +121,30 @@ func (c *OIDCPublicClient) Refresh(ctx context.Context, refreshToken string) (OI
func (c *OIDCPublicClient) VerifyIDToken(ctx context.Context, raw, expectedNonce string) (string, error) {
expectedNonce = strings.TrimSpace(expectedNonce)
if strings.TrimSpace(raw) == "" || expectedNonce == "" {
return "", oidcUnauthorized("ID token validation context is invalid", nil)
return "", oidcUnauthorized("ID_TOKEN_CONTEXT_INVALID", "ID token validation context is invalid", nil)
}
if _, _, err := c.configuration(ctx); err != nil {
return "", oidcUnauthorized("ID token provider discovery failed", err)
return "", oidcUnauthorized("ID_TOKEN_DISCOVERY_FAILED", "ID token provider discovery failed", err)
}
c.mu.Lock()
verifier := c.idTokenVerifier
c.mu.Unlock()
if verifier == nil {
return "", oidcUnauthorized("ID token verifier is unavailable", nil)
return "", oidcUnauthorized("ID_TOKEN_VERIFIER_UNAVAILABLE", "ID token verifier is unavailable", nil)
}
token, err := verifier.Verify(c.requestContext(ctx), raw)
if err != nil {
return "", oidcUnauthorized("ID token signature or registered claims are invalid", nil)
return "", oidcUnauthorized("ID_TOKEN_REGISTERED_CLAIMS_INVALID", "ID token signature or registered claims are invalid", nil)
}
if token.Subject == "" || token.Nonce != expectedNonce {
return "", oidcUnauthorized("ID token subject or nonce is invalid", nil)
return "", oidcUnauthorized("ID_TOKEN_SUBJECT_OR_NONCE_INVALID", "ID token subject or nonce is invalid", nil)
}
var claims map[string]any
if err := token.Claims(&claims); err != nil {
return "", oidcUnauthorized("ID token claims are invalid", nil)
return "", oidcUnauthorized("ID_TOKEN_CLAIMS_INVALID", "ID token claims are invalid", nil)
}
if _, ok := numericDateClaim(claims["nbf"]); !ok {
return "", oidcUnauthorized("ID token nbf is missing", nil)
return "", oidcUnauthorized("ID_TOKEN_NBF_MISSING", "ID token nbf is missing", nil)
}
return token.Subject, nil
}