无法根据 Identity Server 4 中的策略(一般授权)访问安全端点

Can't access secure endpoint under a policy (authorization in general works) in 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."); }

登录后可以访问前者,不能访问后者。我的推论是声明 admin 没有正确分配给我的用户,我看不到我遗漏了什么。使用我的访问令牌检查 user info endpoint (/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 scopes declared contains admin as a claim. I've verified that scope to be in the access token. I also added info in an API resource 就像这样(虽然我不确定它是否真的需要这样做)。

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", ... }
};

努力证明:

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

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

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

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

quite a few such methods,可以将它们链接起来进行复杂的安全定义。

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

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

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

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

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

或将其设置为 fallback/default 政策:

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

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


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

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

现在 role 声明将显示为 rolesub 将显示为 sub,如您所料。

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

参考资料