ASP.NET Core微服务间Token授权异常导致401未授权
基于ASP.NET Core搭建微服务架构,包含两个核心服务:负责认证的AuthService和处理专业搜索的SearchService。用户通过AuthService完成认证后,SearchService需要利用请求头中的JWT令牌调用AuthService获取用户详情。但向SearchService发送GET请求时,SearchService调用AuthService会收到401未授权错误,AuthService日志显示“未找到Authorization头”。
关键代码实现
[Authorize] [ApiController] [Route("api/[controller]")] public class ProfessionalSearchController : ControllerBase { private readonly ILogger<ProfessionalSearchController> _logger; private readonly IHttpClientFactory _httpClientFactory; public ProfessionalSearchController(ILogger<ProfessionalSearchController> logger, IHttpClientFactory httpClientFactory) { _logger = logger; _httpClientFactory = httpClientFactory; } [HttpGet("search")] public async Task<IActionResult> SearchProfessionals([FromQuery] string specialisation) { var userId = User.FindFirst("uid")?.Value; if (userId == null) { _logger.LogWarning("User ID not found in token"); return Unauthorized(); } var patient = await GetUserById(userId); if (patient == null) { _logger.LogWarning("Patient not found"); return NotFound("Patient not found."); } // Rest of the code... } private async Task<User> GetUserById(string userId) { var token = Request.Headers["Authorization"].ToString(); if (string.IsNullOrEmpty(token)) { _logger.LogWarning("No token provided."); return null; } var client = _httpClientFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); var response = await client.GetAsync($"http://localhost:5203/api/User/{userId}"); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync<User>(); } var errorContent = await response.Content.ReadAsStringAsync(); _logger.LogWarning($"Failed to get user by ID {userId}, StatusCode: {response.StatusCode}, Error: {errorContent}"); return null; } }
已尝试的排查步骤
- 验证令牌包含必要声明(包括角色)
- 确认
SearchService请求AuthService时Authorization头格式为Bearer {token} - 确保两个服务令牌验证参数(Issuer、Audience、签名密钥)完全一致
- 通过Postman直接调用
AuthService端点,使用同一令牌可正常运行
日志信息
System.Net.Http.HttpClient.Default.LogicalHandler: Information: Start processing HTTP request GET http://localhost:5203/api/User/b4e60ba8-79fa-4c33-a862- f60d06213d6b System.Net.Http.HttpClient.Default.ClientHandler: Information: Sending HTTP request GET http://localhost:5203/api/User/b4e60ba8- 79fa-4c33-a862-f60d06213d6b BackendDocVillage.Startup: Information: Request Method: GET, Path: /api/User/b4e60ba8-79fa-4c33-a862-f60d06213d6b BackendDocVillage.Startup: Information: Received Token: Bearer eyJhbGci.. BackendDocVillage.Startup: Information: Request Method: GET, Path: /api/User/b4e60ba8-79fa-4c33-a862-f60d06213d6b BackendDocVillage.Startup: Information: No Authorization header found. System.Net.Http.HttpClient.Default.ClientHandler: Information: Received HTTP response headers after 38.9651ms - 401 System.Net.Http.HttpClient.Default.LogicalHandler: Information: End processing HTTP request after 52.6424ms - 401 SearchService.Controllers.ProfessionalSearchController: Warning: Failed to get user by ID b4e60ba8-79fa-4c33-a862-f60d06213d6b, StatusCode: Unauthorized, Error: SearchService.Controllers.ProfessionalSearchController: Warning: Patient not found
两个服务均在本地运行:AuthService地址为http://localhost:5203,SearchService地址为http://localhost:5095,且均配置使用JWT Bearer认证。
1. 检查HttpClient的自定义消息处理程序
如果SearchService的HttpClientFactory配置了自定义DelegatingHandler,可能会意外移除Authorization头。检查Program.cs或Startup.cs中的HttpClient注册代码,确保自定义处理程序不会修改或删除请求头。
2. 修正令牌提取逻辑
当前代码中token.Replace("Bearer ", "")存在格式风险,改用更可靠的提取方式:
private async Task<User> GetUserById(string userId) { if (!Request.Headers.TryGetValue("Authorization", out var authHeaderValues)) { _logger.LogWarning("No Authorization header provided."); return null; } var authHeader = authHeaderValues.FirstOrDefault(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { _logger.LogWarning("Invalid Authorization header format."); return null; } var token = authHeader.Substring("Bearer ".Length).Trim(); var client = _httpClientFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.GetAsync($"http://localhost:5203/api/User/{userId}"); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync<User>(); } var errorContent = await response.Content.ReadAsStringAsync(); _logger.LogWarning($"Failed to get user by ID {userId}, StatusCode: {response.StatusCode}, Error: {errorContent}"); return null; }
3. 检查AuthService的CORS配置
如果AuthService启用了CORS,需确保允许接收Authorization头。在AuthService的Program.cs中配置CORS:
builder.Services.AddCors(options => { options.AddPolicy("AllowSearchService", policy => { policy.WithOrigins("http://localhost:5095") .AllowAnyMethod() .AllowAnyHeader(); }); }); // 注意CORS中间件顺序:放在UseRouting之后,UseAuthorization之前 app.UseCors("AllowSearchService");
4. 启用HttpClient详细日志排查
在SearchService的appsettings.json中添加HttpClient调试日志,确认请求头是否正确发送:
{ "Logging": { "LogLevel": { "Default": "Information", "System.Net.Http": "Debug" } } }
查看Debug级别的日志,确认Authorization头是否被包含在请求中。
5. 使用命名HttpClient替代默认客户端
注册命名客户端可避免全局配置影响请求,在SearchService的Program.cs中:
builder.Services.AddHttpClient("AuthServiceClient", client => { client.BaseAddress = new Uri("http://localhost:5203/"); });
在控制器中注入并使用:
private readonly HttpClient _authServiceClient; public ProfessionalSearchController(ILogger<ProfessionalSearchController> logger, IHttpClientFactory httpClientFactory) { _logger = logger; _authServiceClient = httpClientFactory.CreateClient("AuthServiceClient"); } // 在GetUserById中调用 var response = await _authServiceClient.GetAsync($"api/User/{userId}");
内容的提问来源于stack exchange,提问作者Asma123

