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

Blazor Server调用已认证Web API:未授权处理及更优方案咨询

Blazor Server中OpenID认证后调用外部API的优化实现

一、自动注入AccessToken到HttpClient

手动给每个API请求加Authorization头太繁琐,推荐用自定义DelegatingHandler实现自动注入,一次配置全局生效:

1. 编写Token授权Handler

public class TokenAuthorizationHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TokenAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 从当前上下文获取登录后的Access Token
        var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
        if (!string.IsNullOrEmpty(accessToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

2. 注册Handler和HttpClient

在Program.cs中注册必要服务,将Handler绑定到HttpClient:

// 注册HttpContext访问器(Blazor Server默认不提供)
builder.Services.AddHttpContextAccessor();
// 注册自定义Handler
builder.Services.AddScoped<TokenAuthorizationHandler>();

// 注册API服务,绑定Handler
builder.Services.AddHttpClient<IMyApiService, MyApiService>()
    .AddHttpMessageHandler<TokenAuthorizationHandler>();

之后所有通过MyApiService发起的请求,都会自动带上Authorization头,不用再手动处理。

二、优雅处理未授权(401)响应

API返回401时,通常需要尝试刷新Token,失败则引导用户重新登录,推荐两种实现方式:

方式1:在Handler中直接处理401

修改上面的TokenAuthorizationHandler,加入401处理逻辑:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
    if (!string.IsNullOrEmpty(accessToken))
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    }

    var response = await base.SendAsync(request, cancellationToken);

    // 处理未授权响应
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync("refresh_token");
        if (!string.IsNullOrEmpty(refreshToken))
        {
            // 调用Identity Server的Token刷新接口
            var tokenEndpoint = "https://your-identity-server/connect/token";
            var tokenClient = new HttpClient();
            var tokenResponse = await tokenClient.PostAsync(tokenEndpoint, new FormUrlEncodedContent(new Dictionary<string, string>
            {
                ["grant_type"] = "refresh_token",
                ["refresh_token"] = refreshToken,
                ["client_id"] = builder.Configuration["Auth:ClientId"],
                ["client_secret"] = builder.Configuration["Auth:ClientSecret"]
            }), cancellationToken);

            if (tokenResponse.IsSuccessStatusCode)
            {
                var newTokens = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>();
                // 更新上下文的Token
                var authProperties = _httpContextAccessor.HttpContext.Properties;
                authProperties.UpdateTokenValue("access_token", newTokens.AccessToken);
                authProperties.UpdateTokenValue("refresh_token", newTokens.RefreshToken);

                // 重新发送请求
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newTokens.AccessToken);
                response = await base.SendAsync(request, cancellationToken);
            }
            else
            {
                // 刷新失败,登出并跳转登录页
                await _httpContextAccessor.HttpContext.SignOutAsync();
                var navigationManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<NavigationManager>();
                navigationManager.NavigateTo("/login", forceLoad: true);
            }
        }
        else
        {
            // 无Refresh Token,直接登出
            await _httpContextAccessor.HttpContext.SignOutAsync();
            var navigationManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<NavigationManager>();
            navigationManager.NavigateTo("/login", forceLoad: true);
        }
    }

    return response;
}

方式2:用Polly实现重试+401处理

如果需要更灵活的重试策略,比如只重试一次,推荐用Polly库:

  1. 先安装Polly包:Install-Package Polly
  2. 在Program.cs中配置HttpClient时添加Polly策略:
builder.Services.AddHttpClient<IMyApiService, MyApiService>()
    .AddHttpMessageHandler<TokenAuthorizationHandler>()
    .AddPolicyHandler(Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
        .RetryAsync(1, async (result, retryCount, context) =>
        {
            // 重试时执行Token刷新逻辑
            var httpContextAccessor = context.GetService<IHttpContextAccessor>();
            var refreshToken = await httpContextAccessor.HttpContext.GetTokenAsync("refresh_token");
            if (!string.IsNullOrEmpty(refreshToken))
            {
                // 同上面的刷新Token逻辑,获取新Token并更新上下文
                // ...
            }
        }));

三、组件层简化错误处理

把API调用封装到服务类后,组件只需调用服务方法,统一捕获异常即可:

@inject IMyApiService ApiService
@inject NavigationManager NavManager

<button @onclick="LoadData">加载数据</button>

@code {
    private async Task LoadData()
    {
        try
        {
            var data = await ApiService.GetData();
            // 渲染数据
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            NavManager.NavigateTo("/login", forceLoad: true);
        }
        catch (Exception ex)
        {
            // 处理其他错误
        }
    }
}

注意事项

  • 确保IHttpContextAccessor已注册,Blazor Server默认不会自动提供。
  • 刷新Token时,要和你的Identity Server配置匹配(比如是否需要ClientSecret、GrantType是否正确)。
  • Blazor Server中跳转登录页需加forceLoad: true,确保页面重新加载,清除旧的认证状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 01:18:27