您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# ASP.NET 中怎么利用WebApi服务接口防止重复请求
## 引言
在Web应用开发中,重复请求是一个常见但容易被忽视的问题。当用户频繁点击提交按钮、网络延迟导致多次请求或恶意攻击时,都可能产生重复请求。这些重复请求可能导致数据不一致、资源浪费甚至业务逻辑错误。本文将深入探讨在ASP.NET WebAPI中如何有效防止重复请求。
## 一、重复请求的常见场景
### 1.1 用户操作导致的重复请求
- 快速多次点击提交按钮
- 浏览器刷新导致的表单重复提交
- 后退/前进操作引起的请求重发
### 1.2 网络问题引发的重复请求
- 请求超时后的客户端自动重试
- 网络抖动导致的请求重发
### 1.3 恶意攻击行为
- 自动化脚本的暴力请求
- CSRF攻击尝试
## 二、防止重复请求的核心思路
### 2.1 幂等性设计
```csharp
// 示例:幂等的PUT请求
[HttpPut("orders/{id}")]
public IActionResult UpdateOrder(int id, [FromBody] Order order)
{
// 无论调用多少次,结果都相同
_repository.UpdateOrInsert(id, order);
return Ok();
}
为每个请求分配唯一Token,服务端进行校验。
限制单位时间内的请求频率。
public class PreventDuplicateRequestAttribute : ActionFilterAttribute
{
private static readonly ConcurrentDictionary<string, bool> _processingRequests = new();
private const int DefaultTimeoutSeconds = 5;
public override void OnActionExecuting(ActionExecutingContext context)
{
var requestKey = GenerateRequestKey(context);
if (_processingRequests.TryAdd(requestKey, true))
{
// 设置过期时间自动移除
var removalTimer = new Timer(_ =>
{
_processingRequests.TryRemove(requestKey, out _);
}, null, DefaultTimeoutSeconds * 1000, Timeout.Infinite);
context.HttpContext.Items["RemovalTimer"] = removalTimer;
}
else
{
context.Result = new ConflictObjectResult("请求正在处理中,请勿重复提交");
}
}
private string GenerateRequestKey(ActionExecutingContext context)
{
// 组合用户ID+Action+参数作为唯一键
var userId = context.HttpContext.User.Identity.Name ?? "anonymous";
var action = context.ActionDescriptor.RouteValues["action"];
var parameters = JsonSerializer.Serialize(context.ActionArguments);
return $"{userId}_{action}_{parameters}";
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var requestKey = GenerateRequestKey(context);
if (context.HttpContext.Items["RemovalTimer"] is Timer timer)
{
timer.Dispose();
}
_processingRequests.TryRemove(requestKey, out _);
}
}
public class RedisDistributedLock
{
private readonly IDatabase _redisDb;
private readonly TimeSpan _defaultExpiry = TimeSpan.FromSeconds(30);
public RedisDistributedLock(IConnectionMultiplexer redis)
{
_redisDb = redis.GetDatabase();
}
public async Task<bool> AcquireLockAsync(string key, string value)
{
return await _redisDb.LockTakeAsync(key, value, _defaultExpiry);
}
public async Task ReleaseLockAsync(string key, string value)
{
await _redisDb.LockReleaseAsync(key, value);
}
}
// 在Controller中使用
[HttpPost("payments")]
[PreventDuplicateRequest]
public async Task<IActionResult> ProcessPayment([FromBody] PaymentRequest request)
{
var lockKey = $"payment_{request.OrderId}";
var lockValue = Guid.NewGuid().ToString();
if (!await _redisLock.AcquireLockAsync(lockKey, lockValue))
{
return Conflict("订单正在处理中");
}
try
{
// 处理支付逻辑
return Ok();
}
finally
{
await _redisLock.ReleaseLockAsync(lockKey, lockValue);
}
}
// 使用axios拦截器
let pendingRequests = new Map();
axios.interceptors.request.use(config => {
const requestKey = `${config.method}-${config.url}-${JSON.stringify(config.data)}`;
if (pendingRequests.has(requestKey)) {
return Promise.reject(new Error('重复请求已取消'));
}
const cancelToken = new axios.CancelToken(cancel => {
pendingRequests.set(requestKey, cancel);
});
config.cancelToken = cancelToken;
return config;
});
axios.interceptors.response.use(response => {
const requestKey = `${response.config.method}-${response.config.url}-${JSON.stringify(response.config.data)}`;
pendingRequests.delete(requestKey);
return response;
}, error => {
if (!axios.isCancel(error)) {
const requestKey = `${error.config.method}-${error.config.url}-${JSON.stringify(error.config.data)}`;
pendingRequests.delete(requestKey);
}
return Promise.reject(error);
});
public string GenerateRequestHash(HttpRequestMessage request)
{
var contentHash = request.Content != null
? ComputeHash(await request.Content.ReadAsStringAsync())
: string.Empty;
return $"{request.Method}-{request.RequestUri}-{contentHash}";
}
private string ComputeHash(string input)
{
using var sha256 = SHA256.Create();
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToBase64String(bytes);
}
在JWT中包含请求唯一标识(nonce):
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// 其他配置...
ValidateNonce = true,
NonceValidator = (nonce, securityToken, validationParameters) =>
{
var cache = validationParameters.NonceCache;
return !cache.Contains(nonce) && cache.TryAdd(nonce, DateTime.UtcNow);
}
};
});
public class RateLimiter
{
private readonly ConcurrentDictionary<string, LinkedList<DateTime>> _requestLogs = new();
private readonly int _maxRequests;
private readonly TimeSpan _timeWindow;
public bool TryAcquire(string clientId)
{
var now = DateTime.UtcNow;
var windowStart = now - _timeWindow;
var requestTimes = _requestLogs.GetOrAdd(clientId, _ => new LinkedList<DateTime>());
lock (requestTimes)
{
// 移除过期记录
while (requestTimes.Count > 0 && requestTimes.First.Value < windowStart)
{
requestTimes.RemoveFirst();
}
if (requestTimes.Count < _maxRequests)
{
requestTimes.AddLast(now);
return true;
}
}
return false;
}
}
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
ActionFilter | 单机环境 | 实现简单,零依赖 | 不适用于分布式环境 |
Redis分布式锁 | 分布式系统 | 可靠性高,性能好 | 需要Redis基础设施 |
前端拦截 | 用户体验优化 | 减少无效请求 | 无法防止恶意绕过 |
滑动窗口限流 | API流量控制 | 精细控制请求频率 | 实现复杂度较高 |
防止重复请求是构建健壮WebAPI的重要环节。通过本文介绍的各种方案,开发者可以根据实际业务场景选择合适的技术组合。在ASP.NET Core的灵活架构下,我们可以通过中间件、过滤器、分布式锁等多种方式优雅地解决这一问题,既保证了系统稳定性,又提升了用户体验。 “`
这篇文章提供了从原理到实践的完整解决方案,包含了多种技术实现和对比分析,总字数约2000字。您可以根据实际需求调整代码示例或补充更多细节。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。