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

使用Microsoft.AspNetCore.OData时带认证头的请求缓存问题求助

解决方案:在带JWT认证的OData请求中实现响应缓存

首先,咱们得明确问题根源:默认的ResponseCaching中间件会把Authorization请求头纳入缓存键的计算逻辑。这就导致哪怕是同一个OData资源请求,只要携带的JWT不同(哪怕是可信服务签发的不同有效令牌),中间件都会判定为不同请求,无法复用缓存。结合你的场景(数据15分钟更新+可信服务JWT),咱们可以从「调整缓存键策略」和「主动失效缓存」两方面入手解决。


一、调整缓存策略,忽略Authorization头

根据你使用的.NET版本,有两种主流方案:

方案1:使用传统ResponseCaching(适用于.NET 6及更早版本)

方式A:全局配置自定义缓存键生成器

通过自定义ICacheKeyProvider,在生成缓存键时剔除Authorization头:

// Program.cs / Startup.cs
builder.Services.AddResponseCaching(options =>
{
    // 替换默认的缓存键提供器
    options.CacheKeyProvider = new TrustedServiceCacheKeyProvider(Options.Create(options));
});

// 自定义缓存键提供器
public class TrustedServiceCacheKeyProvider : ICacheKeyProvider
{
    private readonly DefaultCacheKeyProvider _defaultProvider;

    public TrustedServiceCacheKeyProvider(IOptions<ResponseCachingOptions> options)
    {
        _defaultProvider = new DefaultCacheKeyProvider(options);
    }

    public string CreateCacheKey(HttpContext context)
    {
        // 临时移除Authorization头,避免其影响缓存键
        var originalAuthHeader = context.Request.Headers.Authorization;
        context.Request.Headers.Remove(HeaderNames.Authorization);
        
        // 使用默认逻辑生成缓存键
        var cacheKey = _defaultProvider.CreateCacheKey(context);
        
        // 恢复原请求头,不影响后续中间件逻辑
        if (!string.IsNullOrEmpty(originalAuthHeader))
        {
            context.Request.Headers.Authorization = originalAuthHeader;
        }
        
        return cacheKey;
    }
}

方式B:在Action级别精准控制

如果只需要特定OData接口生效,可以用[ResponseCache]属性指定缓存规则,明确排除Authorization头:

// OData控制器的Action上
[ResponseCache(Duration = 900, VaryByHeaders = "Accept, Accept-Language")]
// Duration=900秒(15分钟),仅根据Accept/Accept-Language头区分缓存
public IActionResult Get([FromODataUri] int key)
{
    // 你的OData业务逻辑
    return Ok(yourData);
}

方案2:使用OutputCache(推荐,适用于.NET 7+)

.NET 7引入的OutputCache比传统ResponseCaching更灵活,支持忽略指定请求头、标签式缓存失效等功能:

第一步:注册OutputCache服务并配置策略

// Program.cs
builder.Services.AddOutputCache(options =>
{
    // 定义OData专属缓存策略
    options.AddPolicy("TrustedODataCache", policy =>
    {
        policy.Expire(TimeSpan.FromMinutes(15)); // 默认15分钟过期
        policy.IgnoreRequestHeaders(new[] { HeaderNames.Authorization }); // 忽略Authorization头
        policy.SetVaryByQuery(new[] { "*" }); // 按查询参数区分缓存(按需开启)
    });
});

// 启用OutputCache中间件(注意放在Authentication中间件之后)
app.UseAuthentication();
app.UseAuthorization();
app.UseOutputCache();

第二步:在Action上应用策略

[OutputCache(PolicyName = "TrustedODataCache")]
public IActionResult Get([FromODataUri] int key)
{
    // 你的OData业务逻辑
    return Ok(yourData);
}

二、主动失效缓存(数据更新时立即清除)

默认的时间过期缓存无法满足「数据更新即失效」的需求,咱们可以结合缓存标签或自定义缓存键来主动清除:

针对OutputCache(推荐)

利用标签式缓存,给OData接口的缓存条目打上标签,数据更新时清除对应标签的缓存:

// 修改OData Action的OutputCache属性,添加标签
[OutputCache(PolicyName = "TrustedODataCache", Tags = new[] { "OData_ProductData" })]
public IActionResult GetProducts()
{
    // 业务逻辑
    return Ok(products);
}

// 在数据更新服务中,注入IOutputCacheStore来清除缓存
public class DataUpdateService
{
    private readonly IOutputCacheStore _outputCacheStore;

    public DataUpdateService(IOutputCacheStore outputCacheStore)
    {
        _outputCacheStore = outputCacheStore;
    }

    public async Task UpdateProducts()
    {
        // 执行数据更新操作
        await _productRepository.UpdateAsync(updatedProducts);
        
        // 清除所有标记为"OData_ProductData"的缓存条目
        await _outputCacheStore.EvictByTagAsync("OData_ProductData", CancellationToken.None);
    }
}

针对ResponseCaching

如果用的是传统ResponseCaching,需要复用之前的缓存键生成逻辑,在数据更新时删除对应缓存条目:

// 在数据更新服务中注入IDistributedCache
public class DataUpdateService
{
    private readonly IDistributedCache _distributedCache;
    private readonly ICacheKeyProvider _cacheKeyProvider;

    public DataUpdateService(IDistributedCache distributedCache, ICacheKeyProvider cacheKeyProvider)
    {
        _distributedCache = distributedCache;
        _cacheKeyProvider = cacheKeyProvider;
    }

    public async Task UpdateData()
    {
        // 执行数据更新操作
        await _dataRepository.UpdateAsync(newData);
        
        // 构造模拟请求上下文,生成对应缓存键(需匹配实际请求的路径、方法等)
        var mockHttpContext = new DefaultHttpContext();
        mockHttpContext.Request.Method = HttpMethods.Get;
        mockHttpContext.Request.Path = "/odata/Products";
        
        var cacheKey = _cacheKeyProvider.CreateCacheKey(mockHttpContext);
        // 删除缓存
        await _distributedCache.RemoveAsync(cacheKey);
    }
}

关键注意事项

  1. 先验证JWT再缓存:确保Authentication和Authorization中间件运行在缓存中间件之前,只有合法的可信服务请求才能进入缓存逻辑,避免缓存非法响应。
  2. 缓存键的一致性:自定义缓存键生成器时,要保证生成逻辑在缓存和清除时完全一致,否则无法正确命中或清除缓存。
  3. 分布式缓存适配:如果你的应用是集群部署,务必使用分布式缓存(如Redis)而非内存缓存,避免节点间缓存不一致。

内容的提问来源于stack exchange,提问作者Madison Haynie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:59:52