API 在 Azure 上返回 401 但在 运行 本地时不返回

API returning 401 on Azure but not when run locally

有问题的应用程序是一个网站,它有一个 API 附加在一侧,可以重用为该网站开发的许多数据访问方法。因此,网站 authentication/authorization 和 API 之间可能存在一些干扰。但如果是这样的话,我不明白为什么它在本地有效。

当我在本地 运行 时,我可以测试 API 使用 Swagger 或 Postman 登录,获取 Bearer 令牌并使用它来调用 API 方法。在 Azure 上,虽然登录成功,但下一次调用 API returns a 401:

www-authenticate: Bearer error="invalid_token",error_description="The signature is invalid"

最明显的区别是 appsettings.json,我已将其复制到 Azure 上的应用程序配置 blade:

原始应用程序设置如下所示:

  "Rt5": {
    "BaseAddress": "https://localhost:44357"
  },
  "Azure": {
    "BuildNumber": ""
  },
  "AuthentificationJWT": {
    "Audience": "ddt-ssp",
    "Issuer": "ddt-rt5",
    "SecurityKey": "not so secret"
  },

用法 - ConfigureServices():

        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                options.AccessDeniedPath = "/Home/Unauthorized";
                options.LoginPath = "/User/Login";
                options.LogoutPath = "/User/Logout";
            })
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthentificationJWT:SecurityKey"])),
                    ValidateIssuer = true,
                    ValidIssuer = Configuration["AuthentificationJWT:Issuer"],
                    ValidateAudience = true,
                    ValidAudience = Configuration["AuthentificationJWT:Audience"],
                    ValidateLifetime = true,
                    ValidAlgorithms = new HashSet<string> { SecurityAlgorithms.HmacSha256 },
                    ValidTypes = new HashSet<string> { "JWT" }
                };
            });

        services.AddAuthorization(o =>
        {
            o.AddPolicy(AllowViewExceptions.Policy, p
                => p.Requirements.Add(new AllowViewExceptions.Requirement()));
            o.AddPolicy(AllowSubmitException.Policy, p
                => p.Requirements.Add(new AllowSubmitException.Requirement()));
        });

用法-配置():

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseCookiePolicy();
        app.UseAuthentication();
        app.UseAuthorization();

        if (UseSwagger)
        {
            app.UseSwagger();
            app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); });
        }

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });

坦白:这不是我的代码,我确定为什么我们提到了 Cookie 内容,但似乎确实提供了另一种授权机制。看这里:

    public string GetAuthorization()
    {
        //TODO: use ExtractSecurityToken();
        var claimToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName);
        if (claimToken != null)
        {
            return $"Bearer {claimToken.Value}";
        }
        else if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var headerToken))
        {
            return headerToken;
        }

        return null;
    }

    private JwtSecurityToken ExtractSecurityToken()
    {
        var accessToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName)?.Value;
        if (accessToken == null &&
            _httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var value))
        {
            accessToken = value.ToString().Replace("Bearer ", ""); // TODO: Find a better way then extracting Bearer e.g Get token without scheme
        }

        if (accessToken == null)
        {
            return null;
        }

        return (JwtSecurityToken)new JwtSecurityTokenHandler().ReadToken(accessToken);
    }
  • 问题似乎与 Azure 网站的“身份验证/授权”选项有关,启用后会阻止 Web Api 接受身份验证 header 的请求。禁用该选项并将 Owin 库与 Azure AD 一起使用可提供所需的解决方案。

  • 每个与 Azure-AD 关联的应用程序服务都有一个相应的 Azure-AD Web app/API 类型的应用程序声明。此资源 ID 是应用服务 Azure-AD 应用程序声明中的“App ID URI”。

  • 要调试身份验证问题,请参考此msdn link

原来问题是由应用程序周围的奇怪架构引起的。访问令牌值(包括使用安全密钥计算的 SHA256 校验和)正在另一个应用程序中生成并传递给此应用程序以用于其通信。

当 运行 本地化时,两个应用程序都使用存储在其 appsettings.json 文件中的安全密钥并且是相同的。将这些应用程序部署到 Azure 的管道正在用安全密钥替换随机值,因此它们不同。

我没有删除这里的问题,因为 Suryasri 的 link 以后可能会对其他人有所帮助。