You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server自定义Authorization Handler问题:重复调用与路由参数获取失败

Blazor Server 自定义授权处理器的问题与解决方案

场景背景

正在构建采用Windows身份认证(Negotiate)的Blazor Server应用,核心需求如下:

  • 用户创建的条目需经直属经理或上级经理审批,汇报层级结构存储在数据库中
  • 用户仅能查看自己创建的条目,或是作为创建者的直属/上级经理时查看对应条目

计划通过自定义Authorization Handler实现该逻辑,已创建可查询数据库的通用动态授权处理器并关联到策略,但遇到两个核心问题:

  1. 处理器有时会被多次调用,导致数据库查询的性能浪费
  2. 无法在处理器函数中获取请求的路由参数,始终返回null

现有代码实现

动态授权处理器核心代码

/// <summary>
/// 允许在应用中定义可调用内部函数的"动态"策略
/// </summary>
public class DynamicAuthorizationHandler : AuthorizationHandler<DynamicAuthorizationRequirement>
{
    private readonly IHttpContextAccessor contextAccessor;

    public DynamicAuthorizationHandler(IHttpContextAccessor contextAccessor)
    {
        this.contextAccessor = contextAccessor;            
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DynamicAuthorizationRequirement requirement)
    {
        DynamicPolicyArgs args = new DynamicPolicyArgs()
        {
            PolicyDescription = requirement.Description,
            PolicyName = requirement.PolicyName,
            AuthorizationHandlerContext = context,
            HttpContext = contextAccessor
        };

        //调用委托函数判断用户是否授权
        var isAuthorized = await requirement.OnAuthorizeAsync(args);
        if (isAuthorized)
        {
            context.Succeed(requirement);
        }
    }
}

public class DynamicAuthorizationRequirement : IAuthorizationRequirement
{
    public DynamicAuthorizationRequirement(string policyName, string policyDescription, Func<DynamicPolicyArgs, Task<bool>> funcAuthorize)
    {       
        PolicyName = policyName;
        Description = policyDescription;
        OnAuthorizeAsync = funcAuthorize;
    }

    public string PolicyName { get; set; }
    public string Description { get; set; }
    public Func<DynamicPolicyArgs, Task<bool>> OnAuthorizeAsync { get; set; }

    public override string ToString()
    {
        return Description;
    }
}

public class DynamicPolicyArgs
{
    public string PolicyName { get; set; }
    public string PolicyDescription { get; set; }
    public AuthorizationHandlerContext AuthorizationHandlerContext { get; set; }
    public IHttpContextAccessor HttpContext { get; set; }
}

/// <summary>
/// 添加动态策略的扩展方法
/// </summary>
public static class DynamicAuthorizationHandlerExtensions
{
    /// <summary>
    /// 添加带匹配执行函数的策略,用于评估授权规则
    /// </summary>
    public static AuthorizationOptions AddDynamicPolicy(this AuthorizationOptions options, 
        string policyName, 
        string policyDescription,
        Func<DynamicPolicyArgs, Task<bool>> funcAuthorize, Action<string, AuthorizationPolicyBuilder> configure = null)
    {
        //创建策略
        AuthorizationPolicyBuilder builder = new AuthorizationPolicyBuilder();
        builder.Requirements.Add(new DynamicAuthorizationRequirement(policyName, policyDescription, funcAuthorize));

        configure?.Invoke(policyName, builder);
        
        var policy = builder.Build();
        options.AddPolicy(policyName, policy);

        return options;
    }
}

策略注册与授权逻辑

builder.Services.AddAuthorization(options =>
{
    options.AddDynamicPolicy("AllowOnlyCreatorOrApprover", "仅允许创建者或审批者查看页面", AuthorizeClaimCreatorOrApproverAsync);
});

private Task<bool> AuthorizeClaimCreatorOrApproverAsync(DynamicPolicyArgs args)
{
    bool result = false;
    //尝试获取路由参数...
    var id = args.HttpContext.GetRouteValue("ID");
    if (id != null)
    {
        //查询数据库逻辑...
    }
    
    return Task.FromResult<bool>(result);
}

页面应用策略

@attribute [Authorize(Policy = "AllowOnlyCreatorOrApprover")]

问题解决方案

1. 解决处理器多次调用的性能浪费

Blazor Server中,AuthorizationHandler可能因组件重复渲染、路由变化等原因被多次触发,可通过以下方式优化:

  • 缓存授权结果:针对当前用户和目标条目ID,使用IMemoryCache或分布式缓存存储授权判断结果,缓存键设计为$"Auth_{User.Identity.Name}_{ItemId}",设置5-10分钟的过期时间,避免重复查询数据库
  • 提前终止已授权逻辑:在HandleRequirementAsync开头判断上下文是否已成功授权,若已授权则直接返回:
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DynamicAuthorizationRequirement requirement)
    {
        if (context.HasSucceeded) return; // 已授权则跳过后续逻辑
        
        // 原有执行逻辑...
    }
    
  • 限制组件触发时机:确保页面组件仅在路由参数变化时重新执行授权检查,而非每次组件渲染都触发

2. 解决无法获取路由参数的问题

Blazor Server的路由是组件层面处理的,IHttpContextAccessor无法直接获取路由参数,可通过两种方式解决:

方式一:从AuthorizationHandlerContext提取路由数据

Blazor会将页面的路由数据附加到AuthorizationHandlerContext的Resource属性中,可强转为RouteData获取参数:

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DynamicAuthorizationRequirement requirement)
{
    if (context.Resource is RouteData routeData)
    {
        var itemId = routeData.Values["ID"]?.ToString();
        // 将itemId传入DynamicPolicyArgs
        DynamicPolicyArgs args = new DynamicPolicyArgs()
        {
            PolicyDescription = requirement.Description,
            PolicyName = requirement.PolicyName,
            AuthorizationHandlerContext = context,
            HttpContext = contextAccessor,
            ItemId = itemId // 需要在DynamicPolicyArgs中新增该属性
        };

        var isAuthorized = await requirement.OnAuthorizeAsync(args);
        if (isAuthorized)
        {
            context.Succeed(requirement);
        }
    }
}

方式二:在页面组件中手动触发授权检查

放弃全局Authorize属性,改为在页面组件的参数变更方法中手动获取路由参数并执行授权:

@page "/item/{ID:int}"
@inject IAuthorizationService AuthorizationService
@inject NavigationManager NavigationManager

@code {
    [Parameter]
    public int ID { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        var authResult = await AuthorizationService.AuthorizeAsync(User, ID, "AllowOnlyCreatorOrApprover");
        if (!authResult.Succeeded)
        {
            NavigationManager.NavigateTo("/unauthorized");
        }
    }
}

同时调整授权策略的委托,支持接收条目ID参数:

private Task<bool> AuthorizeClaimCreatorOrApproverAsync(int itemId)
{
    // 使用itemId查询数据库,判断用户是否为创建者或层级内审批者
    bool result = false;
    // 数据库查询逻辑...
    return Task.FromResult(result);
}

内容的提问来源于stack exchange,提问作者Jeremy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 01:44:55