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

C# gRPC客户端拦截器设置Authorization请求头问题排查

解决gRPC客户端拦截器中设置Authorization头的问题

我一眼就看出问题所在了——CallOptions是不可变类型!你调用context.Options.WithHeaders()的时候,它不会修改原有的context.Options对象,而是返回一个全新的CallOptions实例。你的代码里只是调用了这个方法但没把新实例赋值回去,所以原来的请求头根本没变化。

下面是修正后的代码,我会一步步解释关键修改点:

using System;
using Floof.Common.Exceptions.IoC;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Floof.Common.GrpcClient.Interceptors
{
    public class AuthorizationHeaderInterceptor : Interceptor
    {
        private readonly ILogger<AuthorizationHeaderInterceptor> _logger;
        public const string Section = "gRPC";
        public const string Key = "ApiKey";
        private const string AuthorizationHeader = "Authorization";
        private readonly string _apiToken;

        public AuthorizationHeaderInterceptor(
            ILogger<AuthorizationHeaderInterceptor> logger,
            IConfiguration configuration
        )
        {
            if (configuration == null)
                throw new ArgumentNullException(nameof(configuration));
            _logger = logger;
            var apiToken = configuration.GetSection(Section)?[Key];
            if (string.IsNullOrWhiteSpace(apiToken))
                throw new IoCConfigurationException("API key", Section, Key);
            _apiToken = apiToken;
        }

        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncUnaryCallContinuation<TRequest, TResponse> continuation
        )
        {
            // 1. 初始化或复制原有的Metadata
            var metadata = new Metadata();
            if (context.Options.Headers != null)
            {
                // 复制原有请求头,避免丢失其他已设置的头信息
                foreach (var entry in context.Options.Headers)
                {
                    if (!string.Equals(entry.Key, AuthorizationHeader, StringComparison.OrdinalIgnoreCase))
                    {
                        metadata.Add(entry);
                    }
                    else
                    {
                        _logger.LogWarning("将Authorization请求头替换为已配置的Floof API密钥值。");
                    }
                }
            }
            else
            {
                _logger.LogDebug("添加gRPC选项请求头");
            }

            // 2. 添加我们的Authorization头
            metadata.Add(AuthorizationHeader, _apiToken);

            // 3. 创建新的CallOptions(因为原对象不可变)
            var newCallOptions = context.Options.WithHeaders(metadata);

            // 4. 创建新的拦截器上下文,使用更新后的CallOptions
            var newContext = new ClientInterceptorContext<TRequest, TResponse>(
                context.Method,
                context.Host,
                newCallOptions
            );

            // 5. 用新的上下文继续执行调用
            return continuation(request, newContext);
        }
    }
}

关键修改说明:

  • Metadata的处理:我们没有直接修改原有的Headers,而是创建了一个新的Metadata实例,复制了原有头中除Authorization之外的所有条目,这样既保留了其他业务需要的头,又确保我们的API令牌是最终生效的。
  • 不可变的CallOptions:必须通过WithHeaders()创建新的CallOptions,然后用它生成新的ClientInterceptorContext,因为原context对象的属性都是只读的,无法直接修改。
  • 替换逻辑优化:在复制原有头的时候直接过滤掉旧的Authorization头,比先查找再移除更简洁高效。

这样修改后,你的拦截器就能正确地在每个gRPC请求中注入配置好的API令牌了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 22:13:11