首页 > 解决方案 > 将索赔与当前请求进行比较的授权

问题描述

我找不到一种简单的方法来做一些应该很常见的事情。

我有 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。也许到政策开始时,还没有要求?

标签: c#asp.net-core.net-corerazor-pages

解决方案


花了一段时间,但我设法让它适用于 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();

推荐阅读