using IM.Commons; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System.Text; namespace IM.Jwt { public static class WebApplicationJwtExtension { public static IServiceCollection AddJwt(this IServiceCollection services, JwtOptions jwtOptions) { services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtOptions.Issuer, ValidateAudience = true, ValidAudience = jwtOptions.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, // 验证签名秘钥(防止 Token 被篡改) ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.Key)), }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; // SignalR WebSocket 握手时 token 通过 query string 传递(不是 Authorization 头) // Hub 路径为 /chat(见 ConnectorService/Program.cs) if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chat")) { context.Token = accessToken; } return Task.CompletedTask; }, OnAuthenticationFailed = context => { // 在这里打断点,查看 context.Exception // 常见的有:SecurityTokenExpiredException (过期) // 或 SecurityTokenInvalidSignatureException (密钥不对) Console.WriteLine("验证失败原因: " + context.Exception.Message); return Task.CompletedTask; }, OnChallenge = async context => { context.HandleResponse(); context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status401Unauthorized; var result = Result.Fail(ResultCode.AUTH_FAILED); await context.Response.WriteAsJsonAsync(result); }, OnForbidden = async context => { context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status403Forbidden; var result = Result.Fail(ResultCode.PERMISSION_DENIED); await context.Response.WriteAsJsonAsync(result); } }; }); return services; } } }