Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs
glz eb8f40f2ab refactor(auth): 优化JWT认证和Token刷新逻辑
1. 调整JWT令牌过期时间配置,新增JwtTokenExpiryDays配置项
2. 分离Token存储前缀,区分管理后台和微信端Token
3. 新增Redis Token有效性校验逻辑,拦截无效/已注销的Token
4. 简化自动刷新逻辑,改为每次请求刷新Redis Token过期时间
5. 完善相关注释和代码结构
2026-06-09 15:17:16 +08:00

131 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
using QYZH.InteractiveMagazine.Models.Settings;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
namespace QYZH.InteractiveMagazine.Infrastructure.Extensions;
/// <summary>
/// 统一服务注册扩展
/// </summary>
public static class DependencyInjectionExtensions
{
/// <summary>
/// 注册基础设施服务
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configuration">配置</param>
/// <param name="environment">运行环境</param>
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null)
{
AddJwtAuthentication(services, configuration, environment);
services.AddTransient<GlobalExceptionMiddleware>();
services.AddTransient<OperationLogMiddleware>();
}
/// <summary>
/// 配置JWT认证
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configuration">配置</param>
/// <param name="environment">运行环境</param>
private static void AddJwtAuthentication(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null)
{
// 开发环境下跳过 JWT 验证
if (environment?.IsDevelopment() == true)
{
services.AddAuthentication("NoAuth")
.AddScheme<AuthenticationSchemeOptions, NoAuthHandler>("NoAuth", options => { });
return;
}
var jwtSettings = configuration.GetSection("JwtSettings").Get<JwtSettings>()!;
services.AddSingleton(jwtSettings);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtSettings.Issuer,
ValidateAudience = true,
ValidAudience = jwtSettings.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey!)),
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
context.Fail("无效的 Token");
return;
}
// 检查管理后台 Token
var adminToken = await RedisHelper.GetAsync($"InteractiveMagazine:AdminAuth:Token:{userId}");
if (!string.IsNullOrEmpty(adminToken))
{
return;
}
// 检查微信端 Token
var wechatToken = await RedisHelper.GetAsync($"InteractiveMagazine:WeChatAuth:Token:{userId}");
if (!string.IsNullOrEmpty(wechatToken))
{
return;
}
// Redis 中不存在任何 Token认证失败
context.Fail("Token 已失效,请重新登录");
}
};
});
}
}
/// <summary>
/// 开发环境免认证处理器
/// </summary>
public class NoAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public NoAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// 开发环境下始终认证成功
var claims = new[]
{
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "DevUser"),
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "0")
};
var identity = new System.Security.Claims.ClaimsIdentity(claims, Scheme.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}