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

.NET Core如何用自定义授权属性校验路由参数中的用户ID?

你的这个方案完全可行!而且这确实是解决重复校验逻辑的好思路,不过咱们可以把它打磨得更完善一些,同时我也会分享几个更简洁的优化方向,帮你彻底摆脱冗余代码~

一、完善你的自定义授权属性方案

你当前的思路没问题,只需要补充路由参数获取、ID对比的核心逻辑,就能实现通用的用户ID校验。这里给你写一个更健壮的实现:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;

public class AuthorizeUserAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    // 允许自定义路由参数名,默认用"userId"
    private readonly string _userIdParamName;

    public AuthorizeUserAttribute(string userIdParamName = "userId")
    {
        _userIdParamName = userIdParamName;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // 先校验用户是否已登录(继承AuthorizeAttribute的基础授权能力)
        var user = context.HttpContext.User;
        if (!user.Identity.IsAuthenticated)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        // 从Claims中解析当前登录用户ID
        var claimUserId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(claimUserId) || !int.TryParse(claimUserId, out int currentUserId))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        // 从路由中获取传入的目标用户ID
        if (!context.RouteData.Values.TryGetValue(_userIdParamName, out var routeValue) ||
            !int.TryParse(routeValue?.ToString(), out int requestedUserId))
        {
            context.Result = new BadRequestObjectResult("无效的用户ID参数");
            return;
        }

        // 对比两个ID,不一致则返回未授权
        if (requestedUserId != currentUserId)
        {
            context.Result = new UnauthorizedResult();
        }
    }
}

使用起来非常简单,直接在Action上标记即可:

// 默认匹配路由中的"userId"参数
[HttpGet("api/user/{userId}")]
[AuthorizeUser]
public IActionResult GetUser(int userId)
{
    // 业务逻辑
    return Ok();
}

// 如果路由参数名是"id",可以自定义参数名
[HttpGet("api/order/{id}")]
[AuthorizeUser("id")]
public IActionResult GetOrder(int id)
{
    // 业务逻辑
    return Ok();
}
二、更符合框架设计的优化:自定义授权策略

除了自定义属性,你还可以用.NET Core原生的授权策略来实现,这是更贴合框架设计的方案,扩展性更强(比如后续可以叠加其他授权规则):

1. 定义授权需求与处理程序

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;

// 自定义授权需求
public class UserIdRequirement : IAuthorizationRequirement
{
    public string UserIdParamName { get; }

    public UserIdRequirement(string userIdParamName = "userId")
    {
        UserIdParamName = userIdParamName;
    }
}

// 授权需求的处理逻辑
public class UserIdAuthorizationHandler : AuthorizationHandler<UserIdRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserIdRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext filterContext)
        {
            // 解析当前登录用户ID
            var claimUserId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            if (string.IsNullOrEmpty(claimUserId) || !int.TryParse(claimUserId, out int currentUserId))
            {
                context.Fail();
                return Task.CompletedTask;
            }

            // 获取路由中的目标用户ID
            if (!filterContext.RouteData.Values.TryGetValue(requirement.UserIdParamName, out var routeValue) ||
                !int.TryParse(routeValue?.ToString(), out int requestedUserId))
            {
                context.Fail();
                return Task.CompletedTask;
            }

            // 校验通过则标记需求成功
            if (requestedUserId == currentUserId)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }

        return Task.CompletedTask;
    }
}

2. 注册授权策略

在.NET 6+的Program.cs中注册策略和处理程序:

builder.Services.AddAuthorization(options =>
{
    // 注册校验用户自身的策略
    options.AddPolicy("OwnUser", policy =>
        policy.Requirements.Add(new UserIdRequirement()));
    
    // 注册校验订单所属用户的策略(路由参数是"id")
    options.AddPolicy("OwnOrder", policy =>
        policy.Requirements.Add(new UserIdRequirement("id")));
});

// 注册授权处理程序
builder.Services.AddScoped<IAuthorizationHandler, UserIdAuthorizationHandler>();

3. 在Action中使用

[HttpGet("api/user/{userId}")]
[Authorize(Policy = "OwnUser")]
public IActionResult GetUser(int userId)
{
    return Ok();
}

[HttpGet("api/order/{id}")]
[Authorize(Policy = "OwnOrder")]
public IActionResult GetOrder(int id)
{
    return Ok();
}
三、小场景优化:模型绑定自动注入当前用户ID

如果你的很多Action不仅需要校验,还需要直接使用当前用户ID,那可以用自定义模型绑定来简化:

using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Security.Claims;

public class CurrentUserIdAttribute : ModelBinderAttribute
{
    public CurrentUserIdAttribute() : base(typeof(CurrentUserIdModelBinder))
    {
    }
}

public class CurrentUserIdModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var user = bindingContext.HttpContext.User;
        var claimUserId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        
        if (string.IsNullOrEmpty(claimUserId) || !int.TryParse(claimUserId, out int currentUserId))
        {
            bindingContext.ModelState.AddModelError(bindingContext.FieldName, "用户身份验证无效");
            return Task.CompletedTask;
        }

        bindingContext.Result = ModelBindingResult.Success(currentUserId);
        return Task.CompletedTask;
    }
}

使用时直接注入当前用户ID,再对比参数:

[HttpGet("api/user/{userId}")]
[Authorize]
public IActionResult GetUser(int userId, [CurrentUserId] int currentUserId)
{
    if (userId != currentUserId)
    {
        return Unauthorized();
    }
    // 业务逻辑中可以直接使用currentUserId
    return Ok();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:57:42