You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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的授权过滤器管道实现全局强制校验,所有接口自动覆盖,包括文件下载接口。

  1. 新建自定义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_");
    }
}
  1. 全局注册过滤器
    在项目App_Start/WebApiConfig.cs中添加过滤器注册,所有接口默认开启校验:
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 原有路由、CORS等配置保持不变
        
        // 注册全局ApiKey鉴权过滤器
        config.Filters.Add(new ApiKeyAuthAttribute());
    }
}
  1. 特殊场景处理
  • 不需要鉴权的公开接口(比如健康检查),给对应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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 02:54:25