如何在.Net WebApi控制器校验DownloadFileAsync请求头的api_key
.NET WebApi 全局ApiKey鉴权实现(覆盖文件下载接口)
问题场景
- 现有 .NET WebApi 服务,新增自定义请求头
api_key作为鉴权凭证,所有接口需要校验该字段合法性 - 服务提供异步文件下载能力,客户端使用
WebClient.DownloadFileAsync()实现(依赖内置的进度、完成回调事件),已经在客户端请求头中添加了api_key字段 - 需要在服务端控制器侧实现强制校验,覆盖普通接口和文件下载接口,鉴权失败返回401未授权
现有代码参考
普通接口校验示例(存在逻辑bug)
[HttpPut] public HttpResponseMessage SomeMethod(string param) { var request = Request; if (request.Headers.Contains("api_key")) { string sValue = request.Headers.GetValues("api_key").First(); if (IsValidApiKey(sValue)) { // Return Unauthorized } } }
该示例存在逻辑错误:ApiKey校验通过的分支反而写了返回未授权逻辑,手写分散校验很容易出现这类问题。
普通接口调用代码
using (var client = new HttpClient()) { HttpRequestMessage request = new HttpRequestMessage(); var sParam = "Foo"; request.RequestUri = new Uri($"{BaseUrl}SomeMethod?param={sParam}"); request.Method = HttpMethod.Put; request.Headers.Add("api_key", "SomeKeyValue"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var responseTask = client.SendAsync(request); responseTask.Wait(); }
文件下载调用代码
using (var client = new WebClient()) { client.Headers.Add("api_key", "SomeKeyValue"); client.DownloadProgressChanged += client_DownloadProgressChanged; client.DownloadFileCompleted += client_DownloadFileCompleted; client.DownloadFileAsync(new Uri(url), filename); }
注:这段客户端代码已经正确实现了请求头添加,不需要修改,服务端可直接读取到api_key字段。
推荐实现方案:全局授权过滤器
不需要在每个控制器方法中重复写校验逻辑,通过WebApi的授权过滤器管道实现全局强制校验,所有接口自动覆盖,包括文件下载接口。
- 新建自定义ApiKey鉴权特性
public class ApiKeyAuthAttribute : AuthorizeAttribute { private const string ApiKeyHeader = "api_key"; // 可配置是否兼容从查询参数传递ApiKey,默认仅校验请求头 public bool EnableQueryFallback { get; set; } = false; protected override bool IsAuthorized(HttpActionContext actionContext) { // 优先从请求头读取ApiKey,自动处理大小写不敏感问题 if (actionContext.Request.Headers.TryGetValues(ApiKeyHeader, out var headerKeys) && headerKeys.Any()) { return ValidateApiKey(headerKeys.First().Trim()); } // 兜底从查询参数读取(可选) if (EnableQueryFallback) { var queryParam = actionContext.Request.GetQueryNameValuePairs() .FirstOrDefault(p => p.Key.Equals(ApiKeyHeader, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrWhiteSpace(queryParam.Value)) { return ValidateApiKey(queryParam.Value.Trim()); } } return false; } protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { // 鉴权失败统一返回401 actionContext.Response = actionContext.Request.CreateErrorResponse( HttpStatusCode.Unauthorized, "Missing or invalid api_key"); } // 提取公共的ApiKey校验逻辑,替换为实际业务实现(查库、读配置、签名校验等) private bool ValidateApiKey(string key) { // 示例逻辑,实际替换为你的校验规则 return !string.IsNullOrEmpty(key) && key.StartsWith("sk_"); } }
- 全局注册过滤器
在项目App_Start/WebApiConfig.cs中添加过滤器注册,所有接口默认开启校验:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 原有路由、CORS等配置保持不变 // 注册全局ApiKey鉴权过滤器 config.Filters.Add(new ApiKeyAuthAttribute()); } }
- 特殊场景处理
- 不需要鉴权的公开接口(比如健康检查),给对应Action或Controller添加
[AllowAnonymous]特性即可跳过校验 - 文件下载接口不需要做任何特殊修改,管道会自动在进入方法前完成鉴权,校验通过才会执行下载逻辑返回文件流
备选方案:单独接口校验
如果不想做全局校验,仅给文件下载等特定接口加校验,直接在Action方法内实现校验逻辑即可,注意修正判断分支:
[HttpGet] public HttpResponseMessage DownloadFile(string fileToken) { // 校验请求头ApiKey if (!Request.Headers.TryGetValues("api_key", out var keys) || !keys.Any()) { return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Missing api_key"); } if (!ValidateApiKey(keys.First())) { return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid api_key"); } // 原有文件下载逻辑 string localPath = GetFileByToken(fileToken); var stream = new FileStream(localPath, FileMode.Open, FileAccess.Read); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) }; response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = Path.GetFileName(localPath) }; return response; }
注意事项
- HTTP请求头规范是大小写不敏感的,不要用字符串完全匹配的方式判断头是否存在,优先使用
TryGetValues方法读取 - 如果服务端配置了CORS跨域,需要在允许的自定义头列表中加入
api_key,否则浏览器会拦截跨域预检请求 WebClient添加的Headers会在所有异步请求(包括DownloadFileAsync)中自动携带,不需要额外处理请求事件手动加头- ApiKey校验逻辑不要硬编码在控制器中,建议抽成公共服务,方便后续更换校验规则(比如换成签名校验、权限范围校验)
内容的提问来源于stack exchange,提问作者JohnB
相关产品推荐
相关产品推荐

