我正在ASP.NET Core 2.1上开发一个授权系统,该系统在授予访问权限之前需要资源级别和作用域一致性。也就是说,我必须是一本书的作者(可以有多个),并且必须具有必要的范围(“ write.book”,“ read.book”,“ delete.book”等)。我在Startup.cs
中成功配置了JWT,并且在传递无效令牌时收到了401s
。我遇到的问题是强制范围。当访问令牌仅需要一个必需的作用域,但始终失败时,访问令牌包含多个作用域policy.RequireClaim("scope", "write.book")
,"write.book delete.book"
就可以工作。如何配置策略以要求一个范围列表,该列表可能是访问令牌包含的范围的子集?我没有看到任何接受范围列表的Policy
方法,因此我假设框架只是在执行字符串比较,这就是授权失败的原因。 write.book != write.book delete.book
。需要说明的是,如果策略仅要求一个作用域write.book
,但是访问令牌write.book delete.book
中存在多个作用域,则授权失败。
以下代码仅在访问令牌包含一个范围的情况下有效,而在存在多个范围的情况下失败。
authorization.AddPolicy("writeBookPolicy", policy => {
policy.RequireAuthenticatedUser().AddAuthenticationSchemes("Bearer")
.RequireClaim("scope", "write.book").Build();
});
{"scope": "write.book"} // Works
{"scope": "write.book delete.book"} //Fails
答案 0 :(得分:0)
您必须使用更复杂的索赔检查。请改用RequireAssertion()
并解析范围声明:
var scopes = new[] { "write.book", "delete.book" };
builder.RequireAssertion(context => {
var claim = context.User.FindFirst("scope");
if(claim == null) { return false; }
return claim.Value.Split(' ').Any(s =>
scopes.Contains(s, StringComparer.OrdinalIgnoreCase)
);
});
我写了一个扩展方法,虽然不易阅读,但更易于使用:
private static readonly IEnumerable<string> _scopeClaimTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"http://schemas.microsoft.com/identity/claims/scope",
"scope"
};
public static AuthorizationPolicyBuilder RequireScope(this AuthorizationPolicyBuilder builder, params string[] scopes) {
return builder.RequireAssertion(context =>
context.User
.Claims
.Where(c => _scopeClaimTypes.Contains(c.Type))
.SelectMany(c => c.Value.Split(' '))
.Any(s => scopes.Contains(s, StringComparer.OrdinalIgnoreCase))
);
}
答案 1 :(得分:0)
我创建的这些扩展方法允许您添加需要所有范围或任何范围的范围策略
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
public static class AuthorizationPolicyBuilder_Extentions
{
public static void AddPolicyScope_AllowedScopes(this AuthorizationOptions authorizationOptions, string policyName, params string[] allowedScopes) =>
authorizationOptions.AddPolicy(policyName, policyBuilder => policyBuilder.RequireClaim("scope", allowedScopes));
public static void AddPolicyScope_AllRequiredScopes(this AuthorizationOptions authorizationOptions, string policyName, params string[] requiredScopes) =>
authorizationOptions.AddPolicy(policyName, policyBuilder => policyBuilder.RequireScopes(requiredScopes));
private static AuthorizationPolicyBuilder RequireScopes(this AuthorizationPolicyBuilder builder, params string[] requiredScopes) =>
builder.RequireAssertion(context => {
var userScopes = GetUserScopes(context);
return requiredScopes.All(scope => userScopes.Contains(scope, StringComparer.CurrentCulture));
});
private static IEnumerable<string> GetUserScopes(this AuthorizationHandlerContext context) =>
context?.User?
.Claims
.Where(c => c.Type.Equals("scope"))
.SelectMany(c => c.Value.Split(' ')) ?? new List<string>();
}