如何限制ASP.NET Web API被篡改请求参数进行非法查询
解决方案建议
以下是几种针对.NET API防止请求参数篡改、实现细粒度资源访问控制的方案:
1. 基于声明的资源授权策略
在用户的Bearer Token中嵌入其有权访问的资源标识(如资源ID/GUID列表),通过.NET的授权策略验证请求参数是否在用户的权限范围内:
- 定义自定义授权需求:
public class ResourceAccessRequirement : IAuthorizationRequirement { public string ResourceIdClaimType { get; } public ResourceAccessRequirement(string claimType) => ResourceIdClaimType = claimType; } - 实现授权处理器:
public class ResourceAccessHandler : AuthorizationHandler<ResourceAccessRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourceAccessRequirement requirement) { // 从请求路由中获取资源ID var routeData = context.Resource as HttpContext; var resourceId = routeData?.Request.RouteValues["id"]?.ToString(); // 从用户Claims中提取允许访问的资源列表 var allowedResources = context.User.FindAll(requirement.ResourceIdClaimType).Select(c => c.Value); if (!string.IsNullOrEmpty(resourceId) && allowedResources.Contains(resourceId)) { context.Succeed(requirement); } return Task.CompletedTask; } } - 注册策略并在API上启用:
控制器方法上标注:services.AddAuthorization(options => { options.AddPolicy("ResourceAccess", policy => policy.Requirements.Add(new ResourceAccessRequirement("AllowedResourceIds"))); }); services.AddSingleton<IAuthorizationHandler, ResourceAccessHandler>();[Authorize(Policy = "ResourceAccess")]
2. 请求参数签名验证
前端对关键请求参数(资源ID、时间戳等)生成专属签名,后端重新计算签名并比对,不一致则拒绝请求:
- 前端示例(CryptoJS实现):
const secretKey = 'your-shared-secret'; // 建议从后端动态获取临时密钥 const resourceId = 'target-resource-guid'; const timestamp = Date.now().toString(); const signature = CryptoJS.HmacSHA256(`${resourceId}:${timestamp}`, secretKey).toString(); // 请求时携带签名与时间戳 fetch(`/api/resources/${resourceId}`, { headers: { 'Authorization': `Bearer ${token}`, 'X-Signature': signature, 'X-Timestamp': timestamp } }); - 后端验证逻辑:
[HttpGet("{id}")] public IActionResult GetResource(string id) { var signature = Request.Headers["X-Signature"].FirstOrDefault(); var timestamp = Request.Headers["X-Timestamp"].FirstOrDefault(); var secretKey = "your-shared-secret"; // 与前端一致的密钥 // 验证时间戳有效性(防止重放攻击) if (Math.Abs(long.Parse(timestamp) - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) > 300000) // 5分钟有效期 { return Unauthorized(); } // 重新计算签名并比对 var computedSignature = Convert.ToBase64String(new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)) .ComputeHash(Encoding.UTF8.GetBytes($"{id}:{timestamp}"))); if (signature != computedSignature) { return Forbid(); } // 后续业务逻辑 return Ok(yourResource); }
3. 资源专属临时令牌
当用户打开第三方页面时,后端生成仅能访问该特定资源的临时令牌(有效期短),页面使用此令牌调用API:
- 生成临时令牌:
[HttpGet("generate-resource-token/{resourceId}")] public IActionResult GenerateResourceToken(string resourceId) { // 先验证用户基础权限 if (!UserHasAccessToResource(User, resourceId)) { return Forbid(); } // 生成仅包含目标资源ID的JWT令牌,有效期15分钟 var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes("resource-token-secret"); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new Claim[] { new Claim("ResourceId", resourceId) }), Expires = DateTime.UtcNow.AddMinutes(15), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return Ok(new { ResourceToken = tokenHandler.WriteToken(token) }); } - API验证临时令牌:
需在Startup中注册对应的认证方案。[HttpGet("{id}")] [Authorize(AuthenticationSchemes = "ResourceTokenScheme")] public IActionResult GetResource(string id) { var resourceIdFromToken = User.FindFirst("ResourceId")?.Value; if (resourceIdFromToken != id) { return Forbid(); } return Ok(yourResource); }
4. 自定义授权属性
编写自定义属性,在授权阶段直接验证请求参数与用户权限的匹配:
public class ResourceAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var user = context.HttpContext.User; if (!user.Identity.IsAuthenticated) { context.Result = new UnauthorizedResult(); return; } // 从路由获取资源ID var resourceId = context.RouteData.Values["id"]?.ToString(); if (string.IsNullOrEmpty(resourceId)) { context.Result = new BadRequestResult(); return; } // 验证用户是否有权访问该资源 if (!UserHasAccessToResource(user, resourceId)) { context.Result = new ForbidResult(); } } private bool UserHasAccessToResource(ClaimsPrincipal user, string resourceId) { // 替换为实际权限校验逻辑,比如从数据库查询用户权限 var allowedResources = user.FindAll("AllowedResourceIds").Select(c => c.Value); return allowedResources.Contains(resourceId); } }
使用时直接标注在控制器方法上:[ResourceAuthorize]
5. 数据层自动过滤
在数据访问层(如EF Core)添加全局查询过滤器,自动根据当前用户身份过滤数据,从根源上防止非法访问:
public class YourDbContext : DbContext { private readonly IHttpContextAccessor _httpContextAccessor; public YourDbContext(DbContextOptions<YourDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options) { _httpContextAccessor = httpContextAccessor; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Resource>() .HasQueryFilter(r => r.OwnerId == _httpContextAccessor.HttpContext.User.FindFirstValue("UserId") || _httpContextAccessor.HttpContext.User.IsInRole("Admin")); } }
这种方式下,即使请求参数被篡改,数据层也只会返回用户有权访问的资源。
内容的提问来源于stack exchange,提问作者Shivanka
相关产品推荐
相关产品推荐

