首页 > 解决方案 > 无法根据 Identity Server 4 中的策略(一般授权)访问安全端点

问题描述

在我的 API 的Startup.cs中,我有以下授权策略。

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  services.AddAuthentication(...);
  ...
  services.AddAuthorization(options =>
  {
      options.AddPolicy("VerySecurePolicy", policy =>
      {
          policy.RequireClaim("admin");
      });
      options.AddPolicy("VaguelySecurePolicy", policy =>
      {
          policy.RequireAuthenticatedUser();
      });
  });
}

然后,我保护了两种操作方法,一种具有无参数属性,一种具有指定的策略。

[Authorize, HttpGet("regular")]
public IActionResult GetRegularData() { return Ok("This is regular level data."); }

[Authorize(Policy = "VerySecurePolicy"), HttpGet("admin")]
public IActionResult GetAdminData() { return Ok("This is admin level data."); }

登录后,我可以访问前者,但不能访问后者。我的推论是索赔管理员没有正确分配给我的用户,我看不到我缺少什么。使用我的访问令牌检查用户信息端点/connect/userinfo)会给我 ID、电子邮件等,但不是角色admin。检查令牌本身根本没有显示声明数组(只有范围和通常的声明,如subexp等)。

这是TestUser登录的实例。

yield return new TestUser
{
    SubjectId = "37cfad39-e4da-486b-a8db-a752565125f8", ...
    Claims = new List<Claim>
    {
        new Claim(JwtClaimTypes.Email, "fakey.uno@touchtech.comm"), ...
        new Claim(JwtClaimTypes.Role, "admin")
    }
};

声明的API 范围之一包含admin作为声明。我已验证该范围在访问令牌中。我还在这样的API 资源中添加了信息(尽管我不确定它是否真的需要它)。

yield return new ApiScope("test_scope_a1", "Test scope A1", new[] { "admin" });

yield return new ApiResource
{
    ...
    Scopes = new List<string> { "test_scope_a1", ... },
    UserClaims = new List<string> { "admin", ... }
};

努力证明:

标签: c#jwtauthorizationidentityserver4.net-core-3.1

解决方案


您的VerySecurePolicy政策设置不正确。像这样定义策略。注意claimType参数是ClaimTypes.Role,而不是role[1]。

options.AddPolicy("VerySecurePolicy", policy => {
    policy.RequireClaim(claimType: ClaimTypes.Role, "admin"); 
});

或者,如果这样更合适,请使用其他 require 方法。

options.AddPolicy("VerySecurePolicy", policy => {
    policy.RequireRole("admin");
});

很多这样的方法,并且可以将它们链接起来以进行复杂的安全定义。

policy.RequireAuthenticatedUser()
    .RequireClaim("client_id", "spa_client")
    .RequireRole("admin")
    .RequireAssertion(context => context.User.Name == "Konrad");
});

您在原始样本中实际拥有的是admin索赔支票,而不是role: admin索赔。

此外,您可以访问前一个操作,因为您没有指定策略,也没有默认/后备策略。这意味着[Authorize]仅确保用户经过身份验证。

为了使其正常工作,您需要明确指定它:

[Authorize("VaguelySecurePolicy"), HttpGet("admin")]
public IActionResult GetRegularData() { return Ok("This is regular level data."); }

或将其设置为后备/默认策略:

services.AddAuthorization(
    options => {
        options.AddPolicy("VerySecurePolicy", policy => { policy.RequireClaim("role", "admin"); });
        // 
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    }
);

这可确保带有注释的操作[Authorize]在没有特定策略集的情况下受回退策略的约束。


[1]:我们没有使用role声明类型,而是使用它的映射等效项 http://schemas.microsoft.com/ws/2008/06/identity/claims/role。如果您不希望发生此映射,并引用出现在 JWT 中的声明,则需要禁用映射:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(...).AddJwtBearer(...);

现在role声明将显示为role,subsub如您所期望的那样显示。

请注意,这会破坏许多事情,例如,您现在不能User.IsInRole("rolename")用于检查角色,因为它期望角色声明类型为ClaimTypes.Role. 所以我就让它保持原样。

参考


推荐阅读