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

Blazor WASM使用gRPC code-first模式时token过期的正确处理方法咨询

解决步骤

1. 实现自定义gRPC客户端拦截器

继承Interceptor类实现自定义拦截器,在拦截逻辑中完成token有效期校验、强制刷新、调用重试以及未授权跳转逻辑:

public class AuthTokenRefreshInterceptor : Interceptor
{
    private readonly IAccessTokenProvider _accessTokenProvider;
    private readonly NavigationManager _navigationManager;

    public AuthTokenRefreshInterceptor(IAccessTokenProvider accessTokenProvider, NavigationManager navigationManager)
    {
        _accessTokenProvider = accessTokenProvider;
        _navigationManager = navigationManager;
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        return new AsyncUnaryCall<TResponse>(
            HandleResponseAsync(request, context, continuation),
            ResponseHeadersAsync,
            GetStatus,
            GetTrailers,
            Dispose);

        async Task<TResponse> HandleResponseAsync(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            // 预检查token有效性,提前5分钟触发刷新避免接口调用失败
            var tokenResult = await _accessTokenProvider.RequestAccessToken();
            if (!tokenResult.TryGetToken(out var accessToken) || accessToken.Expires < DateTimeOffset.Now.AddMinutes(5))
            {
                var refreshResult = await _accessTokenProvider.RequestAccessToken(new AccessTokenRequestOptions
                {
                    ForceRefresh = true
                });
                if (!refreshResult.TryGetToken(out _))
                {
                    _navigationManager.NavigateToLogin();
                    throw new RpcException(new Status(StatusCode.Unauthenticated, "身份凭证已失效,请重新登录"));
                }
            }
            
            // 执行原调用
            var call = continuation(request, context);
            try
            {
                return await call.ResponseAsync;
            }
            catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated)
            {
                // 兜底逻辑:调用返回未授权时再刷新重试一次
                var refreshResult = await _accessTokenProvider.RequestAccessToken(new AccessTokenRequestOptions { ForceRefresh = true });
                if (refreshResult.TryGetToken(out _))
                {
                    var retryCall = continuation(request, context);
                    return await retryCall.ResponseAsync;
                }
                _navigationManager.NavigateToLogin();
                throw;
            }
        }

        // 基础透传方法,无需修改
        private Task<Metadata> ResponseHeadersAsync() => Task.FromResult<Metadata>(null);
        private Status GetStatus() => Status.DefaultSuccess;
        private Metadata GetTrailers() => new Metadata();
        private void Dispose() { }
    }
}

如果你用到了客户端流、服务端流、双向流类型的gRPC调用,需要额外重写AsyncClientStreamingCall、AsyncServerStreamingCall、AsyncDuplexStreamingCall方法,添加相同的token校验逻辑。

2. 修改Grpc服务注册配置

将自定义拦截器注册到DI容器,并绑定到GrpcChannel中:

public static WebAssemblyHostBuilder AddGrpc(this WebAssemblyHostBuilder builder)
{
    // 注册自定义拦截器
    builder.Services.AddScoped<AuthTokenRefreshInterceptor>();

    builder.Services
        .AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
        .CreateClient(BaseClient));

    builder.Services.AddScoped(services =>
    {
        var baseAddressMessageHandler = services.GetRequiredService<AuthenticationHeaderHandler>();
        baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
        var grpcWebHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
        // 从DI容器获取拦截器
        var refreshInterceptor = services.GetRequiredService<AuthTokenRefreshInterceptor>();
        var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, new GrpcChannelOptions 
        { 
            HttpHandler = grpcWebHandler,
            // 绑定拦截器到Channel
            Interceptors = { refreshInterceptor }
        });
        return channel;
    });

    return builder;
}

3. 可选调整项

  • 可以根据业务需要调整token提前刷新的时间阈值,一般推荐设置为token有效期的1/10
  • 如果你的AuthenticationHeaderHandler已经会自动从IAccessTokenProvider获取最新token,拦截器中不需要手动添加Authorization请求头,仅触发刷新逻辑即可

内容的提问来源于stack exchange,提问作者Мстислав Павлов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 20:15:01