【问题标题】:Asp.Net Core Razor Pages authorization handlers for policies with Authorize attribute on PageModel still run after executing policies set to folders在执行设置为文件夹的策略后,PageModel 上具有 Authorize 属性的策略的 Asp.Net Core Razor Pages 授权处理程序仍然运行
【发布时间】:2021-08-14 23:03:37
【问题描述】:

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

非常感谢任何帮助。

要求:

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 属性应用页面的策略:

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

这是我的启动课,我正在使用 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());
    }
}

}

【问题讨论】:

    标签: c# asp.net-core authorization razor-pages asp.net-core-identity


    【解决方案1】:

    docs 中所述,此行为是设计使然

    如果一个处理程序调用context.Succeedcontext.Fail,所有其他处理程序仍会被调用。这允许需求产生副作用,例如日志记录,即使另一个处理程序已成功验证或未通过需求,也会发生这种情况。当设置为false 时,InvokeHandlersAfterFailure 属性会在调用context.Fail 时使处理程序的执行短路。 InvokeHandlersAfterFailure 默认为 true,在这种情况下会调用所有处理程序。

    如果您不想设置InvokeHandlersAfterFailure 选项,您可以在更具体的处理程序中检查AuthorizationHandlerContext.HasFailed 属性以查看它是否为false 并提前返回。例如:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-15
      • 2018-09-16
      • 2021-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多