我找不到简单的方法来做应该很普遍的事情。
我有Razor页面,我想添加一些授权逻辑以确保请求
https://localhost:5001/admin/subscriptions/123`
123
是任何Guid的地方是{subscriptionId}
。
我使用的是基于cookie的身份验证,因此对于当前请求,我应该有一个声明Name
或类似的声明,并带有一个特定的subscriptionId,例如124
。
我想处理这些订阅,以便在URL中的subscriptionId
与特定声明不匹配时禁止访问。
这很尴尬,但我不知道该如何使用AspNet Core 3.1和Razor页面。
似乎根本不是针对此类情况设计的。我发现的示例认为,我们已经事先知道了索赔应该是什么。例如,在Microsoft官方文档中,它显示了如何将“年龄”声明与硬编码魔术int 21
的最小年龄进行比较。
如果两个值都依赖于请求怎么办?
当我添加
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.RequireAssertion(context =>
{
var claims = context.User.Claims;
var subscriptionId = context.User.FindFirst(x => x.Type == ClaimTypes.Name);
// how can I access here the Request and do the comparison?
}));
});
如果访问上下文,则会看到参数化的URL,而不是请求的真实值。
我看到admin/subscriptions/{subscriptionId}
而不是诸如admin/subscriptions/124
如何访问URL查询?,以便可以将其与声明值进行比较?
更新1:
我在某处读过here关于这种铸造的内容:
context.Resource is AuthorizationFilterContext mvcContext
那是行不通的。资源是AuthorizationHandlerContext
我也尝试了以下方法
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.RequireAssertion(context =>
{
var claims = context.User.Claims;
var subscriptionId = context.User.FindFirst(x => x.Type == ClaimTypes.Name);
var accessor = new HttpContextAccessor();
var url = accessor.HttpContext.GetRouteData();
return false;
}));
});
但HttpContext
为空。也许到政策启动时,还没有任何请求?
答案 0 :(得分:2)
花了一段时间,但我设法使其适用于DotNetCore 3.1 +。
事实证明,我需要注入id002
服务,因此,我决定不使用内联策略功能实现并添加所有内容。
首先是IHttpContextAccessor
IAuthorizationRequirement
不要离开一个空的类,我决定将要查找的声明名称传递给它,这样它就更通用了。
然后public class SubscriptionRequirement
: IAuthorizationRequirement
{
public string ClaimName { get; }
public SubscriptionRequirement(string claimName)
{
ClaimName = claimName;
}
}
可以访问声明(通过cookie或其他方式)和http请求。
AuthorizationHandler
要添加此政策:
public class SubscriptionRequirementHandler
: AuthorizationHandler<SubscriptionRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SubscriptionRequirementHandler(
IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
SubscriptionRequirement subscriptionRequirement)
{
var claims = context.User.Claims;
var subscriptionId =
context
.User
.FindFirst(x => x.Type == subscriptionRequirement.ClaimName)
.Value;
var httpContext = _httpContextAccessor.HttpContext;
var subscriptionIdInUrl = httpContext.Request.RouteValues["subscriptionId"];
if (subscriptionIdInUrl != null)
{
var isAuthorized = subscriptionId == subscriptionIdInUrl.ToString();
if (isAuthorized)
{
context.Succeed(subscriptionRequirement);
return Task.CompletedTask;
}
}
context.Fail();
return Task.CompletedTask;
}
}
我还可以(对于Razor Pages来说是可选的)指定我想将此策略应用于特定文件夹中的所有页面
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.Requirements.Add(new SubscriptionRequirement(ClaimTypes.Name)));
});
最后,非常重要的是,注册处理程序和http
services
.AddRazorPages(
options =>
{
options.Conventions.AuthorizeFolder("/admin", "SameSubscriptionIdPolicy");
options.Conventions.Add(new PageRouteTransformerConvention(new LowerCaseRoutes()));
});
更新2020/06/18:
根据Kirk的评论,注册services.AddSingleton<IAuthorizationHandler, SubscriptionRequirementHandler>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
的一种更好的方法是使用已经可用的扩展名作为单例注册。
HttpContextAccessor