如何借助JWT令牌与Action过滤器获取用户专属内容?
.NET Web API 基于JWT声明的权限过滤实现方案
完全可以通过自定义Action过滤器、资源过滤器或者策略授权来实现自动验证JWT声明与请求资源的匹配,避免重复代码。以下是几种实用方案:
一、自定义Action过滤器(通用参数验证)
创建一个继承ActionFilterAttribute的过滤器,在Action执行前自动验证当前用户的JWT声明与请求中的用户ID/资源ID是否匹配:
public class UserResourceAuthorizationFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { // 从JWT令牌中获取当前用户ID声明 var currentUserId = context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(currentUserId)) { context.Result = new UnauthorizedResult(); return; } // 场景1:验证Action参数中的userId if (context.ActionArguments.TryGetValue("userId", out var paramUserId) && paramUserId?.ToString() != currentUserId) { context.Result = new ForbidResult(); return; } // 场景2:验证路由中的资源ID(比如/api/records/{recordId}) if (context.HttpContext.Request.RouteValues.TryGetValue("recordId", out var routeRecordId)) { // 注入数据仓储验证资源归属(需提前注册仓储服务) var recordRepo = context.HttpContext.RequestServices.GetService<IRecordRepository>(); if (recordRepo != null && !recordRepo.IsRecordOwnedByUser(routeRecordId.ToString(), currentUserId)) { context.Result = new ForbidResult(); return; } } base.OnActionExecuting(context); } }
使用方式
- 全局注册(所有接口生效):在
Program.cs中添加
builder.Services.AddControllers(options => { options.Filters.Add<UserResourceAuthorizationFilter>(); });
- 局部生效(仅指定Controller/Action):
[ApiController] [Route("api/records")] [UserResourceAuthorizationFilter] public class RecordsController : ControllerBase { // 接口逻辑 }
二、策略授权(灵活权限规则)
利用.NET内置的策略授权,定义专属的权限验证逻辑,适合复杂的权限场景:
1. 注册授权策略
在Program.cs中添加:
builder.Services.AddAuthorization(options => { options.AddPolicy("UserOwnsResource", policy => policy.RequireAssertion(context => { var currentUserId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(currentUserId)) return false; // 从HttpContext获取请求中的目标资源ID var httpContext = context.Resource as HttpContext; var targetUserId = httpContext?.Request.RouteValues["userId"]?.ToString(); var targetRecordId = httpContext?.Request.RouteValues["recordId"]?.ToString(); // 这里可扩展多种验证逻辑,比如验证记录归属 if (!string.IsNullOrEmpty(targetUserId)) { return targetUserId == currentUserId; } else if (!string.IsNullOrEmpty(targetRecordId)) { var repo = httpContext.RequestServices.GetService<IRecordRepository>(); return repo?.IsRecordOwnedByUser(targetRecordId, currentUserId) ?? false; } return false; })); });
2. 在接口中使用
[Authorize(Policy = "UserOwnsResource")] [HttpGet("{recordId}")] public async Task<IActionResult> GetRecord(string recordId) { // 无需手动验证,已由策略完成权限检查 var record = await _recordRepo.GetByIdAsync(recordId); return Ok(record); }
三、自动过滤查询数据(列表场景)
如果需要返回用户专属的列表数据,可以通过扩展方法自动拼接过滤条件,避免每个接口重复写Where:
1. 定义实体接口
public interface IUserOwnedEntity { string UserId { get; set; } } // 让你的实体实现该接口 public class Record : IUserOwnedEntity { public string Id { get; set; } public string UserId { get; set; } // 其他属性 }
2. 创建查询扩展方法
public static class QueryableExtensions { public static IQueryable<T> FilterByCurrentUser<T>(this IQueryable<T> query, HttpContext httpContext) where T : IUserOwnedEntity { var currentUserId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(currentUserId)) { return query.Where(x => false); // 无权限返回空集合 } return query.Where(x => x.UserId == currentUserId); } }
3. 在接口中使用
[HttpGet] public async Task<IActionResult> GetMyRecords() { // 自动过滤当前用户的记录 var myRecords = await _dbContext.Records.FilterByCurrentUser(HttpContext).ToListAsync(); return Ok(myRecords); }
这些方案可以根据你的业务场景组合使用,彻底消除手动验证的重复代码,实现权限逻辑的集中管理。
内容的提问来源于stack exchange,提问作者LeonelM
相关产品推荐
相关产品推荐

