"IDX10511: Signature validation failed" 用于 Azure AD SPA 应用程序

"IDX10511: Signature validation failed" for Azure AD SPA application

我有一个源自 Identity Platform 示例的 SPA 应用程序,该示例最初调用图形 API。

我已将端点更改为调用本地 API。

SPA 使用 Azure AD 进行身份验证。

API 示例源自 API 的 VS 2019 项目模板。

、NET 4.7.2 - 没有 .NET Core。

我可以进行身份​​验证,并且在进行网络跟踪时 ID 和访问令牌都存在。

然而,在API这边我得到一个错误:

"IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey, KeyId: '1E50B4475DAC931359D564309A3385FFAB7FB431', InternalId: 'f61f7746-3cff-4557-8b2c-b47fad9cf1e3'. , KeyId: 1E50B4475DAC931359D564309A3385FFAB7FB431"

解码访问令牌显示:

"{
  "typ": "JWT",
  "nonce": "G0Q6_BuYJUfZaBnX-l1Ox1eoncxXRT4KMThFBcn1-VA",
  "alg": "RS256",
  "x5t": "HlC0R12skxNZ1WQwmjOF_6t_tDE",
  "kid": "HlC0R12skxNZ1WQwmjOF_6t_tDE"
}"

谷歌搜索,似乎签名失败是因为 header 中的随机数,这需要 "special processing".

所有验证都由 OWIN 完成。

知道这是什么或如何解决这个问题吗?

我认为这不是配置问题,因为我从未见过任何指定签名的配置。

所以我开始浏览 msal.js - 它是开源的。

"User.Read"(在我使用的示例中定义的范围)在许多地方被硬编码,所以我从示例中删除了这个范围并创建了一个名为 "abc".[=12 的虚拟范围=]

我还针对范围更改重新配置了 Azure AD。

瞧,一切正常。

更有趣的是,header是不同的:

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "HlC0R12skxNZ1WQwmjOF_6t_tDE",
  "kid": "HlC0R12skxNZ1WQwmjOF_6t_tDE"
}

注意没有随机数。

所以我怀疑因为原始样本使用了 Microsoft Graph,"User.Read" 暗示了一些特殊的图形处理,添加了搞砸签名的随机数。

对于reference

如果您仍然需要 User.Read 作用域,您可以通过添加一个在身份验证之前散列 nonce 的中间件来获得成功的签名验证

app.UseMiddleware<HashNonceJwtHeaderMiddleware>();

这里是中间件:

using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;


namespace MyApplication.Api.Infrastructure
{

    public class HashNonceJwtHeaderMiddleware
    {
        private readonly RequestDelegate _next;
        private static bool _hashNonceBeforeValidateToken = true;

        public HashNonceJwtHeaderMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            string accessToken = context.Request.Headers["Authorization"].FirstOrDefault();
            if (string.IsNullOrWhiteSpace(accessToken)) { return; }

            accessToken = accessToken.Substring("Bearer ".Length);
            var tokenHandler = new JwtSecurityTokenHandler();
            var jsonToken = tokenHandler.ReadJwtToken(accessToken);
            string[] parts = accessToken.Split('.');
            string header = parts[0];
            string payload = parts[1];
            string signature = parts[2];

            if (_hashNonceBeforeValidateToken &&
                jsonToken.Header.TryGetValue("nonce", out object nonceAsObject))
            {
                string plainNonce = nonceAsObject.ToString();
                using (SHA256 sha256 = SHA256.Create())
                {
                    byte[] hashedNonceAsBytes = sha256.ComputeHash(
                        System.Text.Encoding.UTF8.GetBytes(plainNonce));
                    string hashedNonce = Encode(hashedNonceAsBytes);
                    jsonToken.Header.Remove("nonce");
                    jsonToken.Header.Add("nonce", hashedNonce);
                    header = tokenHandler.WriteToken(jsonToken).Split('.')[0];

                    accessToken = $"{header}.{payload}.{signature}";

                    context.Request.Headers["Authorization"] = $"Bearer {accessToken}";
                }
            }

            await _next(context);
        }

        private string Encode(byte[] input)
        {
            var output = Convert.ToBase64String(input);
            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-'); // 62nd char of encoding
            output = output.Replace('/', '_'); // 63rd char of encoding
            return output;
        }
    }
}

这里是Auth的配置:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi((options) =>
                {
                    options.Audience = "00000003-0000-0000-c000-000000000000";
                },
                (options) =>
                {
                    options.ClientId = "your app registration client id";
                    options.TenantId = "your subscription tenant id";
                    options.Instance = "https://login.microsoftonline.com/";
                    options.Domain = "the domain of the tenant";
                },
                JwtBearerDefaults.AuthenticationScheme);

编码愉快!