使用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
相关产品推荐
相关产品推荐

