User.HasClaim无法成功读取.NET Core 2.0 Web Api中的声明属性

5

在使用Authorize属性和我在Startup.cs中定义的策略时,我遇到了问题。我编辑了我的控制器以手动检查声明。我可以看到包括正确范围的作用域声明,但是当我手动检查该声明/范围时,它返回false。我正在使用Azure AD B2C作为身份服务器,并成功获得验证令牌。

这是来自我的Startup.cs的代码:

    services.AddAuthorization(options =>
    {
        var policyRead = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.read")
            .Build();
        options.AddPolicy("VendorRead", policyRead);

        var policyWrite = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.write")
            .Build();
        options.AddPolicy("VendorWrite", policyWrite);
    });

    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }) 
        .AddJwtBearer(jwtOptions =>
        {
            jwtOptions.Authority = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/{Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/v2.0/";
            jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
            jwtOptions.RequireHttpsMetadata = true;
            jwtOptions.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/v2.0/",
                ValidAudience = Configuration["AzureAdB2C:ClientId"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AzureAdB2C:ClientSecret"]))
            };
            jwtOptions.Events = new JwtBearerEvents
            {
                OnAuthenticationFailed = AuthenticationFailed,
                OnTokenValidated = TokenValidated
            };
        });

以下是我的控制器代码,我在其中手动检查声明:
// GET: api/Vendor/5
[HttpGet("{id}")]
public async Task<IActionResult> Get(VendorRequest request)
{
    var hasClaim1 = User.HasClaim(c => c.Type == "vendor.read");
    var hasClaim2 = User.HasClaim(c => c.Type == "scope");
    var hasClaim3 = User.HasClaim(c => c.Type == "scp");
    var hasClaim4 = User.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/scope");
    var hasClaim5 = User.HasClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.read");
    var hasClaim7= User.HasClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.write");
    var allowed = await _authorization.AuthorizeAsync(User, "VendorRead");
    if (!allowed.Succeeded)
    {
        return StatusCode(StatusCodes.Status403Forbidden);
    }

唯一返回为true的hasClaim是hasClaim4。
这是我的claim: enter image description here 有什么想法吗?目前我只尝试让vendor.read范围工作。

调试器视图似乎显示,作用域声明不包含字符串数组,而是包含一个由空格分隔的两个作用域的字符串。您可以尝试获取声明值然后执行 value.Split(' ').Contains('vendor.read') ? - juunas
是的,@juunas,那个方法可以用,但我不知道如何在我的Startup.cs中编写这样的策略,因为我不想像在控制器中一样手动编写它。我想使用像Chris Pratt下面提到的装饰。只是为了明确,这是有效的代码行:var hasClaim8 = User.HasClaim(c => c.Type.Contains("scope") && c.Value.Contains("vendor.read")); - stumpykilo
3个回答

6
作用域声明是一个以空格分隔的列表,因此在这种情况下,RequireClaim() 助手将无法工作,但更通用的RequireAssertion() 可以使用。 示例作用域声明
"scp": "demo.read demo.write user_impersonation Test-Value"

Sample RequireAssertion()

services.AddAuthorization(options =>
{
    options.AddPolicy("ScopeCheck", policyBuilder => 
        policyBuilder.RequireAssertion(async handler =>
        {
            var scopeClaim = handler.User.FindFirst("http://schemas.microsoft.com/identity/claims/scope");
            var scopes = scopeClaim?.Value.Split(' ');
            var hasScope = scopes?.Where(scope => scope == "demo.write").Any() ?? false;
            return hasScope;
        }));
});

示例控制器(Controller)

[Authorize("ScopeCheck")]
public class SecureController : Controller
{
    [HttpGet]
    public IActionResult Test()
    {
        return Ok(new { Message = "You are allowed" });
    }
}

完整示例项目 - 访问令牌范围 (RFC 6749 section-3.3)

scope参数的值以空格分隔的区分大小写的字符串列表形式表示。这些字符串由授权服务器定义。如果该值包含多个空格分隔的字符串,则它们的顺序无关紧要,并且每个字符串都会向请求的范围添加一个额外的访问范围。


需要针对作用域声明的RequireClaim助手吗? - spottedmahn
检查 scopeClaim.Issuer 怎么样?这样更安全吗? - HappyNomad

1

为了让它与[Authorize("VendorRead")]属性一起正常工作,我必须执行以下操作:

private Task TokenValidated(TokenValidatedContext arg)
{
    var identity = arg.Principal.Identity as ClaimsIdentity;
    var scopeClaims = identity?.Claims.FirstOrDefault(c => c.Type.Contains("scope"))?.Value.Split(' ').ToList();

    if (scopeClaims != null)
    { 
        foreach (var scope in scopeClaims)
        {
            identity?.AddClaim(new Claim("scope", scope));
        }
    }

    return Task.FromResult(0);
}

我在Startup.cs中添加了这个方法,在令牌验证时调用。当我定义JwtBearerEvents时,你可以看到上面的连接。

0

根据你的代码,看起来 vendor.read 实际上是声明的 ,而不是类型。

此外,值得一提的是,设置策略的整个目的就是让你不必进行这种手动验证。只需在你的操作上使用装饰器:

[Authorize(Policy = "VendorRead")]

1
对,我理解关于装饰器的事情,我这样做只是为了手动测试并查看发生了什么。但是装饰器也不起作用。 - stumpykilo
请注意,vendor.read 在声明值中,但声明值是一个以空格分隔的列表,而不是单个值。 - spottedmahn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接