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

使用IHttpClientFactory拦截GetAsync时无法从IdentityServer4获取AccessToken

问题:调用IdentityServer4获取AccessToken时陷入无限循环

环境与背景

我在.NET 6解决方案中部署了三个项目:

  • IdentityServer:负责颁发Access Token
  • Movies.API:提供受保护的电影资源接口
  • Movies.Client:通过HTTP消息处理器从IdentityServer获取Token,再调用Movies.API的受保护接口

相关代码

IdentityServer客户端配置

public class Config
{
    public static IEnumerable<Client> Clients =>
        new Client[]
        {
            new Client
               {
                    ClientId = "movieClient",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = { "movieAPI" } // 与ApiScopes中的名称一致
               },
            new Client
               {
                   ClientId = "movies_mvc_client",
                   ClientName = "Movies MVC Web App",
                   AllowedGrantTypes = GrantTypes.Code,
                   RequirePkce = false,
                   AllowRememberConsent = false,
                   RedirectUris = new List<string>()
                   {
                       "https://localhost:5002/signin-oidc"
                   },
                   PostLogoutRedirectUris = new List<string>()
                   {
                       "https://localhost:5002/signout-callback-oidc"
                   },
                   ClientSecrets = new List<Secret>
                   {
                       new Secret("secret".Sha256())
                   },
                   AllowedScopes = new List<string>
                   {
                       IdentityServerConstants.StandardScopes.OpenId,
                       IdentityServerConstants.StandardScopes.Profile,

                   }
            }
        };

    public static IEnumerable<ApiScope> ApiScopes =>
       new ApiScope[]
       {
           new ApiScope("movieAPI","Movie API")
       };

// 其余代码...

Movies.Client中调用Movies.API的方法

public async Task<IEnumerable<Movie>> GetMovies()
{
    // ** OPTION 1 **
    var httpClient = _httpClientFactory.CreateClient("MovieAPIClient");

    var request = new HttpRequestMessage(HttpMethod.Get, "/api/movies/");

    // 使用消息处理器
    var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);

    response.EnsureSuccessStatusCode();

    var content = await response.Content.ReadAsStringAsync();

    var movieList = JsonConvert.DeserializeObject<List<Movie>>(content);

    return movieList;
    ...
}

HTTP消息处理器实现

消息处理器核心逻辑为:在请求发送前先从IdentityServer获取Access Token,再将Token附加到请求头中。

Movies.Client的Program.cs配置

// 1- 创建用于访问Movies.API的HttpClient
builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient("MovieAPIClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5001/");
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<AuthenticationDelegatingHandler>();


// 2- 创建用于访问IDP的HttpClient
builder.Services.AddHttpClient("IDPClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5005/");
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddHttpMessageHandler<AuthenticationDelegatingHandler>();


builder.Services.AddSingleton(new ClientCredentialsTokenRequest
{
    Address = "https://localhost:5005/connect/token",
    ClientId = "movieClient",
    ClientSecret = "secret",
    Scope = "movieAPI"
});

问题现象

调试时,每次点击继续按钮都会回到消息处理器的SendAsync()方法,陷入无限循环。

原因分析

问题出在IDPClient的注册配置上:给用于请求IdentityServer获取Token的IDPClient也添加了AuthenticationDelegatingHandler。当消息处理器尝试通过IDPClient请求Token时,该请求会再次触发消息处理器,形成无限递归调用。

解决方案

1. 移除IDPClient的消息处理器

修改Program.cs中IDPClient的注册代码,去掉消息处理器的绑定,因为请求IdentityServer获取Token本身不需要附加Access Token:

// 2- 创建用于访问IDP的HttpClient
builder.Services.AddHttpClient("IDPClient", client =>
{
    client.BaseAddress = new Uri("https://localhost:5005/");
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
});

2. 给消息处理器添加请求过滤逻辑

在AuthenticationDelegatingHandler的SendAsync方法中添加判断,仅对Movies.API的请求执行Token附加逻辑,避免误处理其他请求:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    // 仅处理Movies.API的请求
    if (request.RequestUri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) && request.RequestUri.Port == 5001)
    {
        // 获取Access Token
        var tokenResponse = await _tokenClient.RequestClientCredentialsTokenAsync(_tokenRequest, cancellationToken);
        if (tokenResponse.IsError)
        {
            throw new HttpRequestException($"获取AccessToken失败:{tokenResponse.Error}");
        }

        // 将Token附加到请求头
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);
    }

    // 转发请求
    return await base.SendAsync(request, cancellationToken);
}

3. 验证Token获取逻辑

确保消息处理器中使用的是未绑定消息处理器的IDPClient来请求Token,彻底避免循环触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 08:25:55