溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ASP.NET 中怎么利用WebApi服務接口防止重復請求

發布時間:2021-07-15 14:36:19 來源:億速云 閱讀:1683 作者:Leah 欄目:編程語言
# 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字。您可以根據實際需求調整代碼示例或補充更多細節。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女