[英]ASP.NET Core RequireClaim "scope" with multiple scopes
我正在 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
您必須使用更復雜的索賠檢查。 改用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))
);
}
另一個選擇是將范圍拆分為多個聲明
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new() { ValidateAudience = false };
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents()
{
OnTokenValidated = async (context) =>
{
if (context.Principal?.Identity is ClaimsIdentity claimsIdentity)
{
var scopeClaims = claimsIdentity.FindFirst("scope");
if (scopeClaims is not null)
{
claimsIdentity.RemoveClaim(scopeClaims);
claimsIdentity.AddClaims(scopeClaims.Value.Split(' ').Select(scope => new Claim("scope", scope)));
}
}
await Task.CompletedTask;
}
};
});
我創建的這些擴展方法允許您添加需要所有范圍或任何范圍的范圍策略
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>();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.