You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:26:00