Microsoft Entra ID用户access_token签名验证失败排查求助
Microsoft Entra ID Access Token签名验证失败问题
环境与上下文
- 身份提供商:Microsoft Entra ID
- 令牌类型:Access token(JWT)
- 算法:RS256(采用SHA-256和PKCS#1 v1.5填充的RSA算法)
令牌未被修改,手动确认头部、负载、签名格式均正确,但签名验证环节始终失败。
当前配置
应用令牌的验证配置可正常工作,但用户令牌验证失败,相关配置代码如下:
.AddScheme<JwtBearerOptions, CISJwtBearerHandler>( CISAuthorityTypes.UserAuthenticationType, (o) => { //var validAudience = cisAuthenticationOptions.ApplicationIdentityAudience; o.Challenge = CISAuthorityTypes.UserAuthenticationType; //o.Audience = validAudience; o.IncludeErrorDetails = isDevelopmentMode; o.RequireHttpsMetadata = isDevelopmentMode; o.Authority = msOnlineAuthority.ToString(); o.ConfigurationManager = configurationManager; string[] allowedIssuers = ["https://sts.windows.net/", "https://login.microsoftonline.com/"]; o.TokenValidationParameters = new TokenValidationParameters() { ValidateAudience = false, //ValidAudience = validAudience, //ValidateLifetime = true, ValidateIssuer = false, IssuerSigningKeys = keys, TryAllIssuerSigningKeys = false, ValidateIssuerSigningKey = false, LogValidationExceptions = isDevelopmentMode, ValidateSignatureLast = false, NameClaimType = CISClaimTypes.NameClaimName, RoleClaimType = CISClaimTypes.RoleClaimName, ////IssuerSigningKeyValidatorUsingConfiguration = null, // TODO ////IssuerSigningKeyResolver = null, // TODO AuthenticationType = CISAuthorityTypes.UserAuthenticationType, #region TODO REMOVE DEBUG BLOCK //SignatureValidator = (token, parameters) => { // var tokenHandler = new JwtSecurityTokenHandler(); // var innerJwt = new JwtSecurityToken(token); // if (parameters.IssuerSigningKeys != null) { // var keys = (from i in parameters.IssuerSigningKeys // where i.KeyId == innerJwt.Header.Kid // select i).ToArray(); // foreach (var key in keys) { // if (key is RsaSecurityKey keyAsRsaSecKey) { // JwtSignatureValidator.ValidateSignature(keyAsRsaSecKey, token, null); // } else { // JwtSignatureValidator.ValidateSignature((X509SecurityKey)key, token, null); // } // //try { // // // Create a copy of the parameters with only this key // // var singleKeyParameters = parameters.Clone(); // // singleKeyParameters.IssuerSigningKey = key; // // singleKeyParameters.IssuerSigningKeys = new[] { key }; // // singleKeyParameters.SignatureValidator = null; // // // This will throw if the signature validation fails. // // SecurityToken validatedToken; // // tokenHandler.ValidateToken(token, singleKeyParameters, out validatedToken); // // // If validation succeeded, return the validated token. // // return validatedToken; // //} catch (SecurityTokenInvalidSignatureException) { // // // Try the next key if the current key fails. // //} // } // } // return innerJwt; //}, #endregion IssuerValidator = (issuer, securityToken, parameters) => { var q = from i in allowedIssuers where issuer.StartsWith(i, StringComparison.OrdinalIgnoreCase) select i; if (q.Any()) { var jwtToken = (JwtSecurityToken)securityToken; var tempPrincipal = new ClaimsPrincipal(new ClaimsIdentity(jwtToken.Claims, CISAuthorityTypes.UserAuthenticationType)); if (CISJwtSecurityTokenValidator.ValidateUser(tempPrincipal)) return issuer; } throw new SecurityTokenInvalidIssuerException("Invalid issuer."); } }; } )
自定义签名验证类(排查用)
为双重验证令牌,我编写了一个测试类,但验证结果仍显示失败。该类通过TokenValidationParameters.SignatureValidator委托调用,代码如下:
// TODO: Remove this debug class public static class JwtSignatureValidator { /// <summary> /// Validates the signature of a JWT using an RsaSecurityKey. /// </summary> /// <param name="key">The RsaSecurityKey to use for validation.</param> /// <param name="token">The JWT token string.</param> /// <returns>True if the signature is valid; otherwise, false.</returns> public static bool ValidateSignature(RsaSecurityKey key, string token) { var parts = token.Split('.'); if (parts.Length != 3) throw new ArgumentException("Invalid JWT token format.", nameof(token)); // JWT signing input is the header and payload parts concatenated with a period. string signingInput = $"{parts[0]}.{parts[1]}"; byte[] signingBytes = Encoding.UTF8.GetBytes(signingInput); byte[] signature = Base64UrlDecode(parts[2]); // Get the RSA instance from the key. RSA rsa = key.Rsa; if (rsa == null) { rsa = RSA.Create(); rsa.ImportParameters(key.Parameters); } // Verify the signature with SHA256 and PKCS#1 v1.5 padding. var returnValue = rsa.VerifyData(signingBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // Redundant re-verification with the same padding. returnValue = rsa.VerifyData(signingBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return returnValue; } /// <summary> /// Validates the signature of a JWT using an X509SecurityKey. /// </summary> /// <param name="key">The X509SecurityKey to use for validation.</param> /// <param name="token">The JWT token string.</param> /// <returns>True if the signature is valid; otherwise, false.</returns> public static bool ValidateSignature(X509SecurityKey key, string token) { var parts = token.Split('.'); if (parts.Length != 3) throw new ArgumentException("Invalid JWT token format.", nameof(token)); string signingInput = $"{parts[0]}.{parts[1]}"; byte[] signingBytes = Encoding.UTF8.GetBytes(signingInput); byte[] signature = Base64UrlDecode(parts[2]); // Extract the RSA public key from the certificate. var certificate = key.Certificate; using (RSA rsa = certificate.GetRSAPublicKey()) { var returnValue = rsa.VerifyData(signingBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // Additional verification using RSASignaturePadding.Pss is performed here. returnValue = rsa.VerifyData(signingBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); return returnValue; } } /// <summary> /// Decodes a Base64Url-encoded string. /// </summary> /// <param name="input">The Base64Url-encoded string.</param> /// <returns>The decoded byte array.</returns> private static byte[] Base64UrlDecode(string input) { string padded = input.Replace('-', '+').Replace('_', '/'); switch (padded.Length % 4) { case 2: padded += "=="; break; case 3: padded += "="; break; } return Convert.FromBase64String(padded); } }
问题分析与疑问
尽管令牌由Azure Entra ID正确生成并签名,但上述代码验证始终失败。我排查后发现一个明显问题:在ValidateSignature(X509SecurityKey, string token)方法中,先用符合RS256要求的RSASignaturePadding.Pkcs1验证通过,但紧接着用RSASignaturePadding.Pss重新验证——由于RS256令牌采用PKCS#1 v1.5填充签名,第二次PSS验证必然失败,且结果覆盖了第一次的成功结果。
另外,RsaSecurityKey方法中重复用相同填充验证属于冗余操作,虽不会导致失败,但容易混淆。
想问下有没有人遇到过类似的Azure Entra ID令牌密钥匹配但验证失败的情况?令牌签名过程或证书使用中还有哪些细节可能导致这类问题?
内容的提问来源于stack exchange,提问作者user1154363
相关产品推荐
相关产品推荐

