Asp.Net 在执行设置为文件夹的策略后,PageModel 上具有 Authorize 属性的策略的核心 Razor 页面授权处理程序仍然 运行

Asp.Net Core Razor Pages authorization handlers for policies with Authorize attribute on PageModel still run after executing policies set to folders

我正在使用 Razor Pages 和 Asp.Net Core Identity 创建一个简单的 Asp.Net Core Web 应用程序,我遇到了一个问题,即应用到 PageModel 的策略的授权处理程序在尽管每个授权处理程序都与不同的要求 Class 相关,但对同一页面文件夹应用的策略。我的问题是,即使在文件夹策略授权处理程序中调用 Context.Fail(),未经身份验证的用户似乎仍然可以访问页面,这会触发页面策略授权处理程序,然后抛出异常,因为用户对象为空。我想实现一个简单的流程,其中对页面的请求首先由在它们所属的文件夹上设置的策略验证,好吗?继续页面(和 运行 页面授权处理程序),不行,return 立即禁止响应。

非常感谢任何帮助。

要求:

public class SectionsAccessRequirement : IAuthorizationRequirement
{
    public string SectionName { get; }

    public SectionsAccessRequirement(string Name)
    {
        SectionName = Name;
    }
}   

public class RecordCreateRequirement : IAuthorizationRequirement { }

授权处理程序:

public class SectionsAccessHandler : AuthorizationHandler<SectionsAccessRequirement>
{
    public IHttpContextAccessor _accessor;

    public SectionsAccessHandler(IHttpContextAccessor Accessor) => _accessor = Accessor;
    
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SectionsAccessRequirement requirement)
    {
        // Require authentication
        if (!_accessor.HttpContext.User.Identity.IsAuthenticated) context.Fail();

        // Admin
        if (context.User.HasClaim(c => c.Type == ClaimNameConstants.UserGroup && c.Value == UserGroupConstants.AdminGroup)) context.Succeed(requirement);
                                
        if (requirement.SectionName == SectionNameConstants.AdminSection) context.Fail();

        // Client
        if (requirement.SectionName == SectionNameConstants.ClientSection && context.User.HasClaim(c => c.Type == ClaimNameConstants.ClientSection)) context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

public class RecordCreateAuthorizationHandler : AuthorizationHandler<RecordCreateRequirement>
{
    private readonly UserManager<AppUser> _users;

    public RecordCreateAuthorizationHandler(UserManager<AppUser> Users) => _users = Users;

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordCreateRequirement requirement)
    {
        // User ID
        int sessionUserId = int.Parse(_users.GetUserId(context.User));

        // Privileges
        if (!context.User.HasClaim(c => c.Type == ClaimNameConstants.UserGroup && c.Value == UserGroupConstants.AdminGroup))
        {
            if (context.User.HasClaim(c => c.Type == ClaimNameConstants.ClientCreate)) context.Succeed(requirement);
        }
        else
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

我有这两种扩展方法:

public static class AuthorizationOptionsExtension
{
    public static void AddPolicies(this AuthorizationOptions Options)
    {
        // Folders policies
        Options.AddPolicy(PolicyNameConstants.AdminSectionPolicy, p => p.Requirements.Add(new SectionsAccessRequirement(SectionNameConstants.AdminSection)));
        Options.AddPolicy(PolicyNameConstants.ClientSectionPolicy, p => p.Requirements.Add(new SectionsAccessRequirement(SectionNameConstants.ClientSection)));

        // Pages policies
        Options.AddPolicy(PolicyNameConstants.ClientRecordCreatePolicy, p => p.Requirements.Add(new Client.RecordCreateRequirement()));

        // Fallback policy 
        Options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    }        
}

并且:

public static class RazorPagesOptionsExtension
{
    public static void AddFoldersAuthorization(this RazorPagesOptions Options)
    {
        Options.Conventions.AuthorizeFolder("/Admin", PolicyNameConstants.AdminSectionPolicy);

        Options.Conventions.AuthorizeFolder("/Client", PolicyNameConstants.ClientSectionPolicy);
    }
}

页面的政策是使用授权属性应用的:

[Authorize(Policy = PolicyNameConstants.ClientRecordCreatePolicy)]
public class CreateModel : PageModel
{ ... }

这是我的初创公司 class,我正在使用 Asp.Net Core Identity:

public class Startup
{
    private readonly IConfiguration _config;

    public Startup(IConfiguration config)
    {
        _config = config;
    }

    // Add (and configure) services to the container
    public void ConfigureServices(IServiceCollection services)
    {
        // Global application settings (formerly known as Web.config) and other settings
        services.Configure<ApplicationSettings>(_config);

        // Application
        services.AddApplicationLayerServices();

        services.AddRazorPages();
        services.AddHttpContextAccessor();

        services.Configure<RazorViewEngineOptions>(options => 
        { 
            options.PageViewLocationFormats.Add("/Pages/Shared/Partials/{0}" + RazorViewEngine.ViewExtension);
            options.PageViewLocationFormats.Add("/Pages/Client/Partials/{0}" + RazorViewEngine.ViewExtension);
        });

        // Data store
        services.AddDbContext<DataContext>(options => options.UseOracle(_config.GetConnectionString("AppConnectionString")));

        // Security
        services.AddIdentity<AppUser, IdentityRole<int>>(options => {
            options.Password.RequireUppercase = false;
            options.Password.RequiredLength = 8;
        }).AddEntityFrameworkStores<DataContext>();            
        
        services.ConfigureApplicationCookie(options =>
        {
            // Cookie settings
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(15);

            options.LoginPath = "/SignIn";
            options.AccessDeniedPath = "/FourZeroSomething";
            options.SlidingExpiration = true;
        });

        services.AddAccessControlLayerServices();

        services.AddAuthorization(options => options.AddPolicies());

        services.Configure<RazorPagesOptions>(options => options.AddFoldersAuthorization());
    }

    // Configure the HTTP request pipeline
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
        }

        // Navigation
        app.UseHttpsRedirection();            
        app.UseStaticFiles();            
        app.UseRouting();
        
        // Security
        app.UseAuthentication();            
        app.UseAuthorization();

        // Endpoints
        app.UseEndpoints(endpoints => endpoints.MapRazorPages());
    }
}

}

docs 中所述,此行为是设计:

If a handler calls context.Succeed or context.Fail, all other handlers are still called. This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. When set to false, the InvokeHandlersAfterFailure property short-circuits the execution of handlers when context.Fail is called. InvokeHandlersAfterFailure defaults to true, in which case all handlers are called.

如果您不想设置 InvokeHandlersAfterFailure 选项,您可以检查更具体的处理程序中的 AuthorizationHandlerContext.HasFailed 属性,看看它是否是 false 和 return早。例如:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordCreateRequirement requirement)
{
    if (context.HasFailed)
        return;

    // User ID
    int sessionUserId = int.Parse(_users.GetUserId(context.User));

    // ...
}