首页 > 解决方案 > 使用 Razor Pages 中的约定按区域按不同方案进行授权

问题描述

我的 Razor Pages 应用程序的结构如下:

我想允许匿名访问任何非区域页面(/Pages/ 中的任何内容)。我想对管理区域中的所有页面使用 Windows 身份验证,并通过承载令牌对 Api 中的所有页面进行授权。

我可以直接在 PageModels 上使用 Authorize 属性并指定方案来执行此操作。

//Non-area example
[AllowAnonymous]
public class IndexModel : PageModel
//Admin example
[Authorize(AuthenticationSchemes = "Windows")]
public class IndexModel : PageModel
//API example
[Authorize(AuthenticationSchemes = "ApiKey")]
public class IndexModel : PageModel

然后,我可以为 3 个区域中的每一个创建一个基本 PageModel,并从各自的基本 PageModel 继承每个区域的所有 PageModel。

有没有办法使用约定来完成同样的事情?

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.???
    })

标签: c#authorizationrazor-pages

解决方案


我已经解决了。诀窍是 AuthorizeFilter 可以包含带有构造函数重载的方案。

var authorizeFilter = new AuthorizeFilter(new List<IAuthorizeData> {
    new AuthorizeAttribute()
    {
        AuthenticationSchemes = authenticationSchemes
    }
});

然后我必须编写我自己的 IPageApplicationModelConvention 将在区域级别应用。默认方法适用于文件夹和页面级别。我使用Microsoft.AspNetCore.Mvc.RazorPages的源代码作为指南。

public class AreaModelConvention : IPageApplicationModelConvention
{
    private readonly string _areaName;
    private readonly Action<PageApplicationModel> _action;

    public AreaModelConvention(string areaName, Action<PageApplicationModel> action)
    {
        _areaName = areaName;
        _action = action;
    }

    public void Apply(PageApplicationModel model)
    {
        if(string.Equals(_areaName, model.AreaName, StringComparison.OrdinalIgnoreCase))
        {
            _action(model);
        }
    }
}

我写了一些 PageConventionCollectionExtensions 这就是在Microsoft.AspNetCore.Mvc.RazorPages中完成的。

public static class PageConventionCollectionExtensions
{
    public static PageConventionCollection RequireAuthenticationSchemesForArea(this PageConventionCollection conventions, string areaName, string authenticationSchemes)
    {
        if (conventions == null)
        {
            throw new ArgumentNullException(nameof(conventions));
        }

        if (string.IsNullOrEmpty(areaName))
        {
            throw new ArgumentException(nameof(areaName));
        }

        var authorizeFilter = new AuthorizeFilter(new List<IAuthorizeData> {
            new AuthorizeAttribute()
            {
                AuthenticationSchemes = authenticationSchemes
            }
        });

        conventions.AddAreaModelConvention(areaName, model => model.Filters.Add(authorizeFilter));
        return conventions;
    }

    public static IPageApplicationModelConvention AddAreaModelConvention(this ICollection<IPageConvention> pageConventions, string areaName, Action<PageApplicationModel> action)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        var convention = new AreaModelConvention(areaName, action);

        pageConventions.Add(convention);

        return convention;
    }
}

最后我可以全部注册:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AllowAnonymousToNonareas();
        options.Conventions.RequireAuthenticationSchemesForArea("Admin", "Windows");
        options.Conventions.RequireAuthenticationSchemesForArea("Api", "ApiKey");
    })

注意: AllowAnonymousToNonareas 的代码在这里没有定义,但是非常相似。我用这个 Apply 方法创建了一个 NonareaModelConvention:

public void Apply(PageApplicationModel model)
{
    if (model.AreaName == null)
    {
        _action(model);
    }
}

并编写了类似的扩展方法将其绑定在一起。

请记住为应用打开匿名身份验证和 Windows 身份验证。


推荐阅读