如何借助Identity与RabbitMQ实现微服务间用户认证?
1. 登录后是否需要发队列消息?
只有当你需要在登录成功后触发跨服务的异步操作时才需要,比如:
- 记录登录日志到专门的日志服务
- 同步用户基础信息到Products服务的本地缓存(避免每次调用都查Auth服务)
如果只是完成登录后调用Products服务的接口,完全不需要通过MQ传递信息。
2. 是否需要把JWT发队列给Products服务验证?
不需要。JWT的核心就是自包含、可独立验证:
- Products服务只需要拿到Auth服务的公钥(或对称密钥),就能直接验证JWT的签名有效性、过期时间等,不需要依赖Auth服务或MQ传递验证请求。
一、常规登录+Products服务调用流程(无MQ需求)
这是最常用的场景,不需要用到RabbitMQ:
步骤1:完善Auth服务的JWT生成逻辑
在Auth服务的登录接口中,用户验证通过后,生成包含用户ID、角色、权限等信息的JWT令牌,返回给前端。
示例代码:[HttpPost("login")] public async Task<IActionResult> Login(LoginDto dto) { var user = await _userManager.FindByEmailAsync(dto.Email); if (user == null || !await _userManager.CheckPasswordAsync(user, dto.Password)) return Unauthorized(); // 生成JWT var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Role, string.Join(",", await _userManager.GetRolesAsync(user))) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddHours(1), signingCredentials: creds); return Ok(new { Token = new JwtSecurityTokenHandler().WriteToken(token) }); }步骤2:Products服务配置JWT验证
在Products服务的Program.cs中配置JWT验证,使用和Auth服务一致的密钥、签发者、受众:builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); // 启用认证中间件 app.UseAuthentication(); app.UseAuthorization();之后在需要验证权限的接口上添加
[Authorize]特性即可。步骤3:前端调用流程
前端登录拿到JWT后,每次调用Products服务接口时,将JWT放在请求头的Authorization: Bearer {token}中,Products服务会自动完成令牌有效性验证。
二、需要MQ的场景(比如用户登录后同步信息到Products服务)
如果需要在用户登录后异步同步用户信息到Products服务,可基于你已有的MQ发送函数实现:
步骤1:定义用户信息传输模型
创建一个共享DTO类(可放在类库中让两个服务引用):public class UserLoggedInEvent { public string UserId { get; set; } public string UserName { get; set; } public List<string> Roles { get; set; } }步骤2:Auth服务登录成功后发送MQ消息
在登录接口中,验证通过生成JWT后,序列化UserLoggedInEvent为JSON,调用你的SendMessageAsync发送(建议把队列名改成user-logined-events,更贴合业务):[HttpPost("login")] public async Task<IActionResult> Login(LoginDto dto) { // 省略用户验证和JWT生成逻辑... // 发送用户登录事件到MQ var loginEvent = new UserLoggedInEvent { UserId = user.Id, UserName = user.UserName, Roles = await _userManager.GetRolesAsync(user) }; var message = JsonSerializer.Serialize(loginEvent); await SendMessageAsync(message); return Ok(new { Token = tokenString }); }步骤3:Products服务实现MQ消费者
在Products服务中创建后台服务监听队列,处理用户登录事件:public class UserLoginedConsumer : BackgroundService { private readonly IConnectionFactory _connectionFactory; public UserLoginedConsumer(IConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var connection = await _connectionFactory.CreateConnectionAsync(stoppingToken); using var channel = await connection.CreateChannelAsync(stoppingToken); await channel.QueueDeclareAsync(queue: "user-logined-events", durable: false, exclusive: false, autoDelete: false, arguments: null); var consumer = new AsyncEventingBasicConsumer(channel); consumer.Received += async (model, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); var loginEvent = JsonSerializer.Deserialize<UserLoggedInEvent>(message); // 处理逻辑:比如将用户信息存入本地缓存(Redis/内存缓存) // _cache.Set($"user:{loginEvent.UserId}", loginEvent, TimeSpan.FromHours(1)); await channel.BasicAckAsync(ea.DeliveryTag, multiple: false); }; await channel.BasicConsumeAsync(queue: "user-logined-events", autoAck: false, consumer: consumer); await Task.Delay(Timeout.Infinite, stoppingToken); } }然后在Program.cs中注册消费者和连接工厂:
builder.Services.AddSingleton<IConnectionFactory>(sp => new ConnectionFactory { HostName = "localhost" }); builder.Services.AddHostedService<UserLoginedConsumer>();
内容的提问来源于stack exchange,提问作者user28273827332

