.NET 8自定义Identity登录接口:如何同时返回Bearer Token与用户ID
问题分析
你的代码出现格式异常的原因是:当SignInManager使用IdentityConstants.BearerScheme调用PasswordSignInAsync时,该方法会自动向HTTP响应流写入Bearer Token的JSON内容,随后你的TypedResults.Created又写入了user.Id,导致两个响应内容直接拼接,最终出现}1024这种非法JSON格式。
正确实现方式
要同时返回Bearer Token和用户ID,你需要手动生成Token并构造统一的响应对象,避免依赖PasswordSignInAsync的自动响应行为。以下是.NET 8中的完整实现:
1. 定义登录响应DTO
首先创建一个用于封装返回结果的类:
public class LoginResponse { public string TokenType { get; set; } = "Bearer"; public string AccessToken { get; set; } = string.Empty; public int ExpiresIn { get; set; } public string? RefreshToken { get; set; } public string UserId { get; set; } = string.Empty; }
2. 修改自定义登录接口
替换你的/customlogin接口代码,手动生成JWT Token并返回包含用户ID的统一响应:
using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; app.MapPost("/customlogin", async ( [FromBody] LoginRequest cmd, SignInManager<CustomUser> signInManager, UserManager<CustomUser> userManager, IConfiguration config) => { // 查找用户 var user = await userManager.FindByEmailAsync(cmd.Email); if (user == null) { return Results.BadRequest("无效的登录凭据"); } // 验证密码 var passwordCheckResult = await signInManager.CheckPasswordSignInAsync(user, cmd.Password, lockoutOnFailure: false); if (!passwordCheckResult.Succeeded) { var errorMsg = passwordCheckResult.IsNotAllowed ? "账户未被允许登录" : "邮箱或密码错误"; return Results.BadRequest(errorMsg); } // 生成用户身份声明 var userPrincipal = await signInManager.CreateUserPrincipalAsync(user); var jwtConfig = config.GetSection("Jwt"); // 构建JWT Token var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig["Key"])); var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var jwtToken = new JwtSecurityToken( issuer: jwtConfig["Issuer"], audience: jwtConfig["Audience"], claims: userPrincipal.Claims, expires: DateTime.UtcNow.AddMinutes(int.Parse(jwtConfig["ExpiresInMinutes"])), signingCredentials: signingCredentials); // 生成刷新Token(可选) var refreshToken = await userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "RefreshToken"); await userManager.SetAuthenticationTokenAsync(user, TokenOptions.DefaultProvider, "RefreshToken", refreshToken); // 返回统一响应 return TypedResults.Ok(new LoginResponse { AccessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken), ExpiresIn = int.Parse(jwtConfig["ExpiresInMinutes"]) * 60, RefreshToken = refreshToken, UserId = user.Id }); });
3. 配置JWT参数
在appsettings.json中添加JWT配置:
"Jwt": { "Key": "YourSecureKeyWithAtLeast16Characters", "Issuer": "YourApplicationIssuer", "Audience": "YourApplicationAudience", "ExpiresInMinutes": 60 }
4. 配置JWT认证服务
在Program.cs中注册JWT认证服务:
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();
说明
- 改用
CheckPasswordSignInAsync仅验证密码,避免PasswordSignInAsync自动写入响应流的行为。 - 手动生成JWT Token可以完全控制返回内容,同时包含用户ID和标准的Bearer Token字段。
- 刷新Token的生成和存储是可选功能,若不需要可移除相关代码。
内容的提问来源于stack exchange,提问作者Zoinky
相关产品推荐
相关产品推荐

