using LiveForum.Code.Base; using LiveForum.Code.ExceptionExtend; using LiveForum.Code.JwtInfrastructure.Interface; using LiveForum.Code.Redis.Contract; using LiveForum.Code.Utility; using Mapster; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace LiveForum.Code.JwtInfrastructure { /// /// /// public static class JwtTokenManageExtension { /// /// 添加jwt安全验证,配置 /// /// /// /// public static void AddJwtConfig(this IHostApplicationBuilder builder, Func func) { // 使用 Configure 注册 JWT 配置(支持热更新) builder.Services.Configure(builder.Configuration.GetSection("JwtTokenConfig")); // 注册 JWT 配置的 PostConfigure,设置默认值 builder.Services.PostConfigure(options => { // 如果 Secret 为空,生成一个默认值 if (string.IsNullOrEmpty(options.Secret)) { options.Secret = Guid.NewGuid().ToString("N"); } // 如果 AccessTokenExpiration 小于等于 0,设置默认值 if (options.AccessTokenExpiration <= 0) { options.AccessTokenExpiration = 60; } }); // 注册一个向后兼容的单例服务(使用 IOptionsMonitor 获取最新配置) builder.Services.AddSingleton(sp => { var monitor = sp.GetRequiredService>(); return monitor.CurrentValue; }); // builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.RequireHttpsMetadata = true; options.SaveToken = true; // 从配置中读取初始值(用于中间件初始化) // 注意:TokenValidationParameters 在中间件初始化时设置,但我们可以通过事件处理器使用最新配置进行额外验证 var secret = builder.Configuration["JwtTokenConfig:Secret"]?.tostrnotempty(Guid.NewGuid().ToString("N")) ?? string.Empty; var issuer = builder.Configuration["JwtTokenConfig:Issuer"] ?? string.Empty; var audience = builder.Configuration["JwtTokenConfig:Audience"] ?? string.Empty; //调试使用 //options.Events = new JwtDebugBearerEvents().GetJwtBearerEvents(); options.TokenValidationParameters = new TokenValidationParameters { //是否验证颁发者 ValidateIssuer = true, //是否验证受众 ValidateAudience = true, //指定是否验证令牌的生存期。设置为 true 表示要进行验证。 ValidateLifetime = true, //指定是否验证颁发者签名密钥。设置为 true 表示要进行验证。 ValidateIssuerSigningKey = true, //颁发者(使用初始配置) ValidIssuer = issuer, //受众(使用初始配置) ValidAudience = audience, //指定用于验证颁发者签名的密钥(使用初始配置) IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)), //指定允许令牌的时钟偏移。允许令牌的过期时间与实际时间之间存在的时间差。在这里设置为 5 分钟,表示允许令牌的时钟偏移为 5 分钟。 ClockSkew = TimeSpan.FromMinutes(30) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var rawAuth = context.Request.Headers["Authorization"].ToString(); if (string.IsNullOrWhiteSpace(rawAuth)) { return Task.CompletedTask; } if (rawAuth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { var tokenValue = rawAuth.Substring(7).Trim(); if (string.IsNullOrEmpty(tokenValue) || tokenValue.Equals("undefined", StringComparison.OrdinalIgnoreCase)) { context.NoResult(); } } return Task.CompletedTask; }, OnTokenValidated = async context => { // 注意:中间件的 TokenValidationParameters 在初始化时设置,但 JwtRedisAuthManager 会使用 IOptionsMonitor 获取最新配置 // 因此新生成的 token 和验证逻辑都会使用最新配置 var token = context.Request.Headers.GetAuthorization(); if (string.IsNullOrEmpty(token)) { context.Fail("非法请求接口"); return; } var tokenMd5 = token.ToMD5(); var manage = context.HttpContext.RequestServices.GetRequiredService(); var redis = context.HttpContext.RequestServices.GetRequiredService(); var userInfo = context.HttpContext.RequestServices.GetRequiredService(); string blackUserKey = $"jwt:expire:user:{tokenMd5}"; //判断token是否被加入黑名单 var isUserExpire = await redis.GetAsync(blackUserKey); if (isUserExpire != null && isUserExpire > 2) { context.Fail(new LoginExpiredException(ResponseCode.Unauthorized, "token已失效")); return; } try { var tokenInfo = await manage.DecodeJwtTokenAsync(token); if (userInfo == null) { userInfo = new JwtUserInfoModel(); } tokenInfo.Adapt(userInfo); //userInfo.UserName= tokenInfo.UserName; //userInfo.Claims= tokenInfo.Claims; //userInfo.ExpireAt= tokenInfo.ExpireAt; // } catch (RedisNullException) { //context.Fail(lex); var dataToken = func(token, tokenMd5, context.HttpContext.RequestServices); if (string.IsNullOrEmpty(dataToken)) { if (isUserExpire == null) { isUserExpire = 0; } isUserExpire++; await redis.SetAsync(blackUserKey, isUserExpire.Value, TimeSpan.FromMinutes(5)); context.Fail(new LoginExpiredException(ResponseCode.Unauthorized, "未登录")); return; } var databaseUserInfo = await manage.DecodeBaseJwtTokenAsync(dataToken); databaseUserInfo.Adapt(userInfo); //重新写入缓存 await manage.WriteJwtCacheAsync(userInfo.UserName, token, userInfo.ExpireAt); } catch (Exception ex) { context.Fail(new LoginExpiredException(ResponseCode.Forbidden, ex.Message)); return; } }, // 处理认证失败的事件 OnAuthenticationFailed = context => { var response = context.Response; if (response.HasStarted) { return Task.CompletedTask; } response.Clear(); // 清空现有响应内容 response.StatusCode = StatusCodes.Status200OK; response.ContentType = "application/json"; var payload = context.Exception is LoginExpiredException loginExpired ? loginExpired.ToString() : new BaseResponse(ResponseCode.ParamError, "", null).ToString(); return response.WriteAsync(payload); }, // 在认证失败并被 Challenge 时触发该事件 OnChallenge = context => { context.HandleResponse(); // 确保不再执行默认的挑战响应 var response = context.Response; if (response.HasStarted) { return Task.CompletedTask; } response.Clear(); // 清空现有响应内容 response.StatusCode = StatusCodes.Status200OK; response.ContentType = "application/json"; string payload; if (context.AuthenticateFailure is LoginExpiredException loginExpired) { payload = loginExpired.ToString(); } else { var result = new BaseResponse(ResponseCode.Unauthorized, "用户未登录", null); if (context.AuthenticateFailure != null) { result.Message = context.AuthenticateFailure.Message; } payload = result.ToString(); } return response.WriteAsync(payload); } }; }); //注册一个JwtAuthManager的单例服务 builder.Services.AddScoped(); builder.Services.AddScoped(); } } }