c# - 将索赔与当前请求进行比较的授权
问题描述
我找不到一种简单的方法来做一些应该很常见的事情。
我有 Razor 页面,我想添加一些授权逻辑以确保请求
https://localhost:5001/admin/subscriptions/123`
哪里123
是任何 Guid 是{subscriptionId}
。
我正在使用基于 cookie 的身份验证,因此我应该有一个Name
具有特定订阅 ID 的声明或类似内容,例如124
当前请求。
我想处理这些订阅,以便subscriptionId
在 URL 中的 与特定声明不匹配时禁止访问。
这很尴尬,但我不知道如何使用 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:
我在这里的某个地方读到了一些关于这样的铸造的东西:
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
是 null。也许到政策开始时,还没有要求?
解决方案
花了一段时间,但我设法让它适用于 DotNetCore 3.1+。
事实证明我需要注入IHttpContextAccessor
服务,因此我决定不使用内联策略函数实现并添加所有部分。首先是IAuthorizationRequirement
public class SubscriptionRequirement
: IAuthorizationRequirement
{
public string ClaimName { get; }
public SubscriptionRequirement(string claimName)
{
ClaimName = claimName;
}
}
为了不留下一个空类,我决定将我正在寻找的声明名称传递给它,这样它就更通用了。
然后AuthorizationHandler
能够访问声明(从 cookie 或其他)和 http 请求。
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;
}
}
要添加此策略:
services
.AddAuthorization(
options =>
{
options.AddPolicy(
"SameSubscriptionIdPolicy",
policy =>
policy.Requirements.Add(new SubscriptionRequirement(ClaimTypes.Name)));
});
我还可以(对于 Razor 页面可选)指定我想将此策略应用于特定文件夹中的所有页面
services
.AddRazorPages(
options =>
{
options.Conventions.AuthorizeFolder("/admin", "SameSubscriptionIdPolicy");
options.Conventions.Add(new PageRouteTransformerConvention(new LowerCaseRoutes()));
});
最后,非常重要的是,注册处理程序和 http
services.AddSingleton<IAuthorizationHandler, SubscriptionRequirementHandler>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
2020 年6 月 18 日更新:
根据 Kirk 的评论,一种更好的注册方法HttpContextAccessor
是使用已经可用的扩展名作为单例进行注册。
services.AddHttpContextAccessor();
推荐阅读
- javascript - 小程序直播推送音频比特率
- python - 需要 imagemagick 的帮助
- asp.net-core - 创建 XML 文档文件时出现编译器错误 CS0016
- android-studio - 我的应用程序未在 Android 模拟器中启动
- geopandas - 如何解决缓冲后质心点坐标精度不足的问题?
- python - statsmodels.stats.power.TTestIndPower.solve_power计算的统计功效与PDF曲线下面积之差
- google-chrome - Chrome中的UserAgent桌面/移动/触摸有什么区别
- sql - 使用relid而不是表名查询表
- .net - 如何从 .NET 程序集或源代码中获取 IDL
- tavern - 将发送的请求保存在 Tavern 的变量中