ASP.NET 中怎么利用WebApi服务接口防止重复请求

发布时间:2021-07-15 14:36:19 作者:Leah
来源:亿速云 阅读:1656
# 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();
}

2.2 请求唯一标识

为每个请求分配唯一Token,服务端进行校验。

2.3 请求限流控制

限制单位时间内的请求频率。

三、具体实现方案

3.1 使用ActionFilterAttribute实现防重

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 _);
    }
}

3.2 使用分布式锁(Redis实现)

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);
    }
}

3.3 前端防重方案(配合后端)

// 使用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);
});

四、进阶优化方案

4.1 基于请求内容的哈希校验

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);
}

4.2 结合JWT的防重机制

在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);
            }
        };
    });

4.3 滑动窗口限流算法

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流量控制 精细控制请求频率 实现复杂度较高

六、最佳实践建议

  1. 分层防御:结合前端拦截+后端校验+限流的多层防护
  2. 合理超时:根据业务特点设置合适的锁超时时间
  3. 友好提示:给用户明确的重复请求反馈信息
  4. 监控报警:记录重复请求日志用于分析优化
  5. 性能考量:高频场景考虑使用内存缓存而非分布式锁

结语

防止重复请求是构建健壮WebAPI的重要环节。通过本文介绍的各种方案,开发者可以根据实际业务场景选择合适的技术组合。在ASP.NET Core的灵活架构下,我们可以通过中间件、过滤器、分布式锁等多种方式优雅地解决这一问题,既保证了系统稳定性,又提升了用户体验。 “`

这篇文章提供了从原理到实践的完整解决方案,包含了多种技术实现和对比分析,总字数约2000字。您可以根据实际需求调整代码示例或补充更多细节。

推荐阅读:
  1. ASP.NET WebAPI构建API接口服务实战演练
  2. ASP.NET WebApi服务接口如何防止重复请求实现HTTP幂等性

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

asp.net webapi

上一篇:HAProxy负载均衡是什么

下一篇:Java如何用poi完成Excel导出数据脱敏

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》