.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
相关产品推荐
相关产品推荐

