ASP.NET Core认证:如何拦截SignInManager的AccessTokenResponse
解决方案
方法一:直接通过IAuthenticationService获取令牌并映射自定义类
PasswordSignInAsync本质是验证凭据后触发认证流程生成令牌,但不会直接返回令牌对象。你可以跳过该方法,直接用IAuthenticationService手动完成认证,直接拿到AccessTokenResponse并映射到自定义类。
步骤
- 向处理器注入
IAuthenticationService和IHttpContextAccessor - 验证用户密码后,手动触发认证流程生成令牌
- 从认证结果中提取令牌信息,映射到自定义类
修改后的处理器代码
public class LoginUserHandler : IRequestHandler<LoginUserCommand, BaseResponse<CustomTokenResponse>> { private readonly UserManager<PublicUser> _userManager; private readonly IAuthenticationService _authenticationService; private readonly IHttpContextAccessor _httpContextAccessor; public LoginUserHandler(UserManager<PublicUser> userManager, IAuthenticationService authenticationService, IHttpContextAccessor httpContextAccessor) { _userManager = userManager; _authenticationService = authenticationService; _httpContextAccessor = httpContextAccessor; } public async Task<BaseResponse<CustomTokenResponse>> Handle(LoginUserCommand request, CancellationToken cancellationToken) { var response = new BaseResponse<CustomTokenResponse>(); var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { response.Success = false; response.Message = "Login Failed"; return response; } // 验证密码有效性 var isPasswordValid = await _userManager.CheckPasswordAsync(user, request.Password); if (!isPasswordValid) { response.Success = false; response.Message = "Login Failed"; return response; } // 更新安全戳并创建用户身份标识 await _userManager.UpdateSecurityStampAsync(user); var claimsPrincipal = await _userManager.CreateUserPrincipalAsync(user); // 触发Bearer认证流程 var authProperties = new AuthenticationProperties { IsPersistent = false, AllowRefresh = true }; await _authenticationService.SignInAsync(_httpContextAccessor.HttpContext, IdentityConstants.BearerScheme, claimsPrincipal, authProperties); // 获取认证结果并提取令牌信息 var authResult = await _authenticationService.AuthenticateAsync(_httpContextAccessor.HttpContext, IdentityConstants.BearerScheme); if (authResult.Succeeded && authResult.Properties != null) { response.Data = new CustomTokenResponse { AccessToken = authResult.Properties.GetTokenValue("access_token"), ExpiresIn = int.Parse(authResult.Properties.GetTokenValue("expires_in")), TokenType = authResult.Properties.GetTokenValue("token_type") }; response.Success = true; } else { response.Success = false; response.Message = "Failed to generate token"; } return response; } }
自定义类示例
// 自定义令牌响应类 public class CustomTokenResponse { public string AccessToken { get; set; } public int ExpiresIn { get; set; } public string TokenType { get; set; } } // 带数据的基础响应类 public class BaseResponse<T> : BaseResponse { public T Data { get; set; } } // 基础响应类 public class BaseResponse { public bool Success { get; set; } public string Message { get; set; } }
修改后的控制器代码
[HttpPost("Login")] [AllowAnonymous] public async Task<Results<Ok<CustomTokenResponse>, ProblemHttpResult>> Login(LoginUserCommand command) { var response = await Mediator.Send(command); if (!response.Success) { return TypedResults.Problem(response.Message); } return TypedResults.Ok(response.Data); }
方法二:通过认证事件拦截AccessTokenResponse
如果想保留PasswordSignInAsync的使用方式,可以在JWT认证配置中添加事件,将令牌信息存入HttpContext,之后在处理器中取出映射。
步骤
- 在
Program.cs中配置JWT认证事件,存储令牌信息 - 在处理器中通过
IHttpContextAccessor获取令牌并映射
认证配置代码(Program.cs)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 你的JWT基础配置(如密钥、颁发者等) 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"])) }; // 添加认证成功事件,存储令牌信息 options.Events = new JwtBearerEvents { OnAuthenticationSucceeded = context => { if (context.Properties != null) { context.HttpContext.Items["AccessTokenResponse"] = new CustomTokenResponse { AccessToken = context.Properties.GetTokenValue("access_token"), ExpiresIn = int.Parse(context.Properties.GetTokenValue("expires_in")), TokenType = context.Properties.GetTokenValue("token_type") }; } return Task.CompletedTask; } }; });
修改后的处理器代码
public class LoginUserHandler : IRequestHandler<LoginUserCommand, BaseResponse<CustomTokenResponse>> { private readonly UserManager<PublicUser> _userManager; private readonly SignInManager<PublicUser> _signInManager; private readonly IHttpContextAccessor _httpContextAccessor; public LoginUserHandler(UserManager<PublicUser> userManager, SignInManager<PublicUser> signInManager, IHttpContextAccessor httpContextAccessor) { _userManager = userManager; _signInManager = signInManager; _signInManager.AuthenticationScheme = IdentityConstants.BearerScheme; _httpContextAccessor = httpContextAccessor; } public async Task<BaseResponse<CustomTokenResponse>> Handle(LoginUserCommand request, CancellationToken cancellationToken) { var response = new BaseResponse<CustomTokenResponse>(); var user = await _userManager.FindByNameAsync(request.Username); if (user == null) { response.Success = false; response.Message = "Login Failed"; return response; } var result = await _signInManager.PasswordSignInAsync(user, request.Password, false, true); if (!result.Succeeded) { response.Success = false; response.Message = "Login Failed"; return response; } // 从HttpContext中取出拦截到的令牌信息 if (_httpContextAccessor.HttpContext.Items.TryGetValue("AccessTokenResponse", out var tokenObj) && tokenObj is CustomTokenResponse tokenResponse) { response.Data = tokenResponse; response.Success = true; } else { response.Success = false; response.Message = "Failed to retrieve token"; } return response; } }
关键说明
- 两种方式都能实现令牌信息的获取与自定义类映射,让Swagger识别强类型响应
- 方法一更直接,手动控制认证流程,适合需要完全掌控令牌逻辑的场景
- 方法二更贴合现有代码结构,改动量小,适合保留原有
PasswordSignInAsync调用的场景
内容的提问来源于stack exchange,提问作者Matt MacDonald
相关产品推荐
相关产品推荐

