ASP.NET Core 6微服务中RabbitMQ RPC获取登录用户名失败问题
问题描述
我有两个微服务:
- 负责授权认证的Identity Service(运行在localhost:7096,基于Identity系统配置)
- 为已认证用户提供资源的Application Service(运行在localhost:7141)
当通过RabbitMQ RPC模式从Identity Service获取已登录用户的用户名时出现异常:
- Identity Service提供
get-logged-in-user-name端点,通过自定义扩展方法ClaimPrincipleExtension.GetUsername获取当前登录用户的用户名 - 直接调用该端点功能正常,但通过Application Service的RabbitMQ消费者发起HTTP请求调用时抛出异常
- 若在端点中返回硬编码字符串(如"ExampleUser")则一切正常
相关代码
RabbitMQ消费者代码
private async void ConsumerReceived(object sender, BasicDeliverEventArgs e) { var response = ""; var body = e.Body.ToArray(); var props = e.BasicProperties; var replyProps = _channel.CreateBasicProperties(); replyProps.CorrelationId = props.CorrelationId; try { var wee = _serviceProvider.GetService<IHttpClientFactory>(); var client = wee.CreateClient(); var loggedUserName = await client.GetAsync("https://localhost:7096/get-logged-in-user-name"); response = await loggedUserName.Content.ReadAsStringAsync(); } catch (Exception E) { Console.WriteLine(E); response = "null"; } finally { var responseBytes = Encoding.UTF8.GetBytes(response); _channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: replyProps, body: responseBytes); _channel.BasicAck(deliveryTag: e.DeliveryTag, multiple: false); } }
扩展方法代码
public static class ClaimPrincipleExtension { public static string GetUsername(this ClaimsPrincipal user) { return user.FindFirst(ClaimTypes.Name).Value; } }
端点代码
[HttpGet("get-logged-in-user-name")] public string GetLoggedInUserName() { string username = User.GetUsername(); //!< -获取当前登录用户名 return username; //!< -在此处触发RPC服务端与客户端错误 // return "ExampleUser"; //! <-返回该硬编码值则一切正常 }
错误堆栈信息
at Shared.Abstraction.Extensions.ClaimPrincipleExtension.GetUsername(ClaimsPrincipal user) in /Users/michal/Desktop/MyDateApp/Shared/Extensions/ClaimPrincipleExtension.cs:line 10 at IdentityServer.Application.Controllers.UserController.GetLoggedInUserName() in /Users/michal/Desktop/MyDateApp/MicroServices/IdentityServer/IdentityServer.Application/Controllers/UserController.cs:line 41 at lambda_method3(Closure , Object , Object[] ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
请求头
Host: localhost:7096
问题分析与解决方案
核心原因
从错误堆栈可知,异常发生在ClaimPrincipleExtension.GetUsername方法第10行:user.FindFirst(ClaimTypes.Name)返回null,访问.Value时触发空引用异常。
直接调用端点正常,是因为浏览器/客户端请求携带了认证令牌(如JWT Cookie或Authorization头),Identity Service能识别用户并填充ClaimsPrincipal;而RabbitMQ消费者发起的HTTP请求未携带任何认证信息,Identity Service无法识别用户,User对象不存在ClaimTypes.Name声明。
解决方案
在HTTP请求中携带认证令牌
当Application Service发送RabbitMQ RPC请求时,需将用户的认证令牌(如原始请求中的JWT)作为消息体传递给消费者。消费者拿到令牌后,在发起HTTP请求时添加Authorization头:// 从RabbitMQ消息体解析用户JWT令牌 var token = Encoding.UTF8.GetString(body); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var loggedUserName = await client.GetAsync("https://localhost:7096/get-logged-in-user-name");优化扩展方法,添加空值检查
修改ClaimPrincipleExtension.GetUsername方法,避免空引用异常,同时给出明确错误提示:public static class ClaimPrincipleExtension { public static string GetUsername(this ClaimsPrincipal user) { var nameClaim = user.FindFirst(ClaimTypes.Name); if (nameClaim == null) { throw new InvalidOperationException("当前用户未包含用户名声明"); // 或返回默认值:return string.Empty; } return nameClaim.Value; } }为端点添加授权策略
确保get-logged-in-user-name端点添加授权特性,未认证请求直接返回401,避免走到获取用户名的逻辑:[HttpGet("get-logged-in-user-name")] [Authorize] // 添加授权特性 public string GetLoggedInUserName() { string username = User.GetUsername(); return username; }
内容的提问来源于stack exchange,提问作者Micheal841
相关产品推荐
相关产品推荐

