无法使用 AAD/Microsoft-Identity id_token 上的 System.IdentityModel.Tokens.Jwt 库验证签名

Posted

技术标签:

【中文标题】无法使用 AAD/Microsoft-Identity id_token 上的 System.IdentityModel.Tokens.Jwt 库验证签名【英文标题】:Cannot validate signature using System.IdentityModel.Tokens.Jwt library on AAD/Microsoft-Identity id_token 【发布时间】:2021-11-29 04:18:22 【问题描述】:

我在 Azure 中使用启用了 Azure Active Directory 身份验证的 WebApp,并且正在从此处的 /.auth/me 页面中提取 id_token

据我了解,当我将其粘贴到 JWT.io 时,它会通过在 https://login.microsoftonline.com/tenantId/v2.0/.well-known/openid-configuration

上查找 OpenId 配置来验证签名

然后使用 jwks_uri (https://login.microsoftonline.com/tenantId/discovery/v2.0/keys) 从此处的 JWT 标头中找到基于 child 的公共签名密钥(x5c 数组):

但是,当我尝试在 C# 中复制此场景时,我无法使用 System.IdentityModel.Tokens.Jwt 库验证签名。

以下是我的一些尝试和结果:

尝试

        var idToken = "id_token";
        var signingKey = "x5c[0]";


        SecurityToken validatedToken;
        ClaimsPrincipal claimsPrincipal;

        var handler = new JwtSecurityTokenHandler();

        var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----"));

        var tokenValidationParameters = new TokenValidationParameters()
        
            IssuerSigningKey = symmetricSecurityKey,
            ValidateIssuerSigningKey = true,

            ValidateLifetime = false,
            ValidateAudience = false,
            ValidateIssuer = false
        ;

        try
        
            claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
            Console.WriteLine($"claimsPrincipal.Identity.Name");
        
        catch (Exception ex)
        
            Console.WriteLine(ex.Message);
        

结果

IDX10501:签名验证失败。无法匹配键:孩子: '系统.字符串'。捕获的异常:'System.Text.StringBuilder'。 令牌:'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'。

尝试

        var kid = "l3sQ-50cCH4xBVZLHTGwnSR7680";
        var idToken = "id_token";
        var signingKey = "x5c[0]";


        SecurityToken validatedToken;
        ClaimsPrincipal claimsPrincipal;

        var handler = new JwtSecurityTokenHandler();

        var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----"))  KeyId = kid, ;

        var tokenValidationParameters = new TokenValidationParameters()
        
            IssuerSigningKey = symmetricSecurityKey,
            ValidateIssuerSigningKey = true,

            ValidateLifetime = false,
            ValidateAudience = false,
            ValidateIssuer = false
        ;

        try
        
            claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
            Console.WriteLine($"claimsPrincipal.Identity.Name");
        
        catch (Exception ex)
        
            Console.WriteLine(ex.Message);
        

结果

IDX10511:签名验证失败。尝试过的键: 'System.Text.StringBuilder'。孩子:'System.String'。例外 抓到:'System.Text.StringBuilder'。令牌: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'。

我也尝试了没有 "-----BEGIN CERTIFICATE-----\n""\n-----END CERTIFICATE--- --" 添加到签名密钥并获得相同的结果以及尝试使用 ASCII 编码而不是 UTF8

==== 更新 ====

我能够使用 Microsoft.IdentityModel.ProtocolsMicrosoft.IdentityModel.Protocols.OpenIdConnect 库来验证令牌,并使用以下代码创建签名密钥:

var issuer = jwtToken.Issuer;
var audiences = jwtToken.Audiences;
var issuerOpenIdUri = $"issuer/.well-known/openid-configuration";

string stsDiscoveryEndpoint = issuerOpenIdUri;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever()); 
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;

...然后使用 OpenIdConnectConfiguration 将密钥分配给 TokenValidatetionParameters

var tokenValidationParameters = new TokenValidationParameters()

   ValidAudiences = audiences,
   ValidIssuer = issuer,

   IssuerSigningKeys = config.SigningKeys,
   ValidateIssuerSigningKey = true,

   ValidateLifetime = true,
   ValidateAudience = true,
   ValidateIssuer = true
;

但是我仍然很好奇为什么我最初的尝试行不通。我想更好地了解幕后发生的事情。

【问题讨论】:

【参考方案1】:

Microsoft.IdentityModel.Tokens.X509SecurityKey 在其 KeyId 中使用 base 16 中的证书指纹。 Azure Active Directory KeyId 使用 base 64 中的证书指纹。 要使用与 Azure Active Directory 使用的相同密钥 ID 格式,可以(就像您所做的那样)使用 Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever 并以这种方式使用签名密钥

myTokenValidationParameters.IssuerSigningKeys = myOpenIdConnectionConfiguration.SigningKeys;

当“aud”声明与仅是客户端 ID 的受众不匹配时,也可能发生该错误,并且可以通过在 AAD 和具有适当受众和范围的代码中提供适当的范围以及通过授予同意来缓解此错误。

【讨论】:

以上是关于无法使用 AAD/Microsoft-Identity id_token 上的 System.IdentityModel.Tokens.Jwt 库验证签名的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 StorageClass 配置卷 - 无法获取存储帐户的存储密钥

Worklight Studio 和本地开发,有时无法使用 Java 类,有时无法使用 HTML 文件

ADB无法使用解决办法

Ubuntu 80端口无法使用-非root用户无法使用1024以下端口

无法在 SQL Server 视图中使用工作查询:“IS”无法识别“>”无法识别

LINUX下的mail\mailx为啥无法使用外部SMTP发邮件