如何用Identity Framework实现ASP.NET Core Web API多方式登录授权
基于ASP.NET Core Identity规范实现多方式登录流程
一、基础配置:集成Identity与JWT认证
先确保项目正确集成ASP.NET Core Identity并配置JWT认证:
- 注册Identity服务
在Program.cs中添加以下配置:
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // 注册Identity,可使用自定义User模型扩展字段 builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // 配置JWT认证 builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = 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(Convert.FromBase64String(builder.Configuration["Jwt:Key"])) }; }); builder.Services.AddAuthorization();
- 启用认证授权中间件
在Program.cs的管道中添加:
app.UseAuthentication(); app.UseAuthorization();
二、用UserManager处理第三方登录用户的查找与创建
第三方登录核心是通过第三方唯一标识查找或创建本地用户,用UserManager实现更规范:
1. 定义统一第三方用户模型
适配Google、Facebook、Apple的用户信息格式:
public class ExternalUserInfo { public string Provider { get; set; } // 标识第三方平台:"Google"/"Facebook"/"Apple" public string ProviderUserId { get; set; } // 第三方平台的唯一用户ID public string Email { get; set; } public string UserName { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } }
2. 封装查找/创建用户逻辑
private async Task<ApplicationUser> FindOrCreateExternalUserAsync(ExternalUserInfo externalUser) { // 先通过第三方平台标识和用户ID查找本地用户 var existingUser = await _userManager.FindByLoginAsync(externalUser.Provider, externalUser.ProviderUserId); if (existingUser != null) return existingUser; // 未找到则通过邮箱匹配已有用户 existingUser = await _userManager.FindByEmailAsync(externalUser.Email); if (existingUser != null) { // 将第三方登录关联到已有用户 await _userManager.AddLoginAsync(existingUser, new UserLoginInfo( externalUser.Provider, externalUser.ProviderUserId, externalUser.Provider)); return existingUser; } // 创建新用户 var newUser = new ApplicationUser { UserName = externalUser.UserName, Email = externalUser.Email, EmailConfirmed = true // 第三方登录默认邮箱已验证 }; var createResult = await _userManager.CreateAsync(newUser); if (!createResult.Succeeded) throw new InvalidOperationException($"创建用户失败:{string.Join(", ", createResult.Errors.Select(e => e.Description))}"); // 添加第三方登录关联 await _userManager.AddLoginAsync(newUser, new UserLoginInfo( externalUser.Provider, externalUser.ProviderUserId, externalUser.Provider)); // 初始化用户角色、积分等业务逻辑 await _userManager.AddToRoleAsync(newUser, "User"); return newUser; }
三、用SignInManager生成规范JWT令牌
SignInManager的CreateUserPrincipalAsync方法可自动生成包含用户身份、角色的Claims,替代手动拼接逻辑:
1. 封装Token生成服务
public class TokenService { private readonly SignInManager<ApplicationUser> _signInManager; private readonly IConfiguration _configuration; private readonly UserManager<ApplicationUser> _userManager; public TokenService(SignInManager<ApplicationUser> signInManager, IConfiguration configuration, UserManager<ApplicationUser> userManager) { _signInManager = signInManager; _configuration = configuration; _userManager = userManager; } public async Task<AccessTokenResponse> GenerateAccessTokenAsync(ApplicationUser user) { // 生成包含用户所有身份信息的ClaimsPrincipal var principal = await _signInManager.CreateUserPrincipalAsync(user); // 添加自定义业务Claims(如用户ID、积分等) var customClaims = new List<Claim> { new Claim("UserId", user.Id), // new Claim("Points", user.Points.ToString()) // 示例:若用户模型有积分字段 }; var claims = principal.Claims.Union(customClaims); var key = new SymmetricSecurityKey(Convert.FromBase64String(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expiration = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:ExpirationInHours"]!)); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: expiration, signingCredentials: creds); var jwt = new JwtSecurityTokenHandler().WriteToken(token); var refreshToken = await GenerateRefreshTokenAsync(user); return new AccessTokenResponse { AccessToken = jwt, ExpiresIn = (int)(expiration - DateTime.UtcNow).TotalSeconds, RefreshToken = refreshToken.Token }; } private async Task<RefreshToken> GenerateRefreshTokenAsync(ApplicationUser user) { // 生成并存储刷新令牌(需自行实现RefreshToken实体和存储逻辑) var refreshToken = new RefreshToken { Token = Guid.NewGuid().ToString(), UserId = user.Id, Expires = DateTime.UtcNow.AddDays(7) }; // _context.RefreshTokens.Add(refreshToken); // await _context.SaveChangesAsync(); return refreshToken; } }
2. 注册Token服务
在Program.cs中添加:
builder.Services.AddScoped<TokenService>();
四、重构第三方登录控制器(以Google为例)
[ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly IGoogleAuthService _googleAuthService; private readonly UserManager<ApplicationUser> _userManager; private readonly TokenService _tokenService; public AuthController(IGoogleAuthService googleAuthService, UserManager<ApplicationUser> userManager, TokenService tokenService) { _googleAuthService = googleAuthService; _userManager = userManager; _tokenService = tokenService; } [HttpPost("google-login")] public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginRequest request) { try { var googleUser = await _googleAuthService.GetUserDetails(request.IdToken); if (googleUser == null) return BadRequest("Google服务返回无效用户信息"); var externalUser = new ExternalUserInfo { Provider = "Google", ProviderUserId = googleUser.Sub, Email = googleUser.Email, UserName = googleUser.Email.Split('@')[0], FirstName = googleUser.GivenName, LastName = googleUser.FamilyName }; var user = await FindOrCreateExternalUserAsync(externalUser); var tokenResponse = await _tokenService.GenerateAccessTokenAsync(user); return Ok(tokenResponse); } catch (InvalidOperationException ex) { return BadRequest(ex.Message); } catch (Exception) { return StatusCode(StatusCodes.Status500InternalServerError, "服务器内部错误"); } } private async Task<ApplicationUser> FindOrCreateExternalUserAsync(ExternalUserInfo externalUser) { var existingUser = await _userManager.FindByLoginAsync(externalUser.Provider, externalUser.ProviderUserId); if (existingUser != null) return existingUser; existingUser = await _userManager.FindByEmailAsync(externalUser.Email); if (existingUser != null) { await _userManager.AddLoginAsync(existingUser, new UserLoginInfo( externalUser.Provider, externalUser.ProviderUserId, externalUser.Provider)); return existingUser; } var newUser = new ApplicationUser { UserName = externalUser.UserName, Email = externalUser.Email, EmailConfirmed = true }; var createResult = await _userManager.CreateAsync(newUser); if (!createResult.Succeeded) throw new InvalidOperationException($"创建用户失败:{string.Join(", ", createResult.Errors.Select(e => e.Description))}"); await _userManager.AddLoginAsync(newUser, new UserLoginInfo( externalUser.Provider, externalUser.ProviderUserId, externalUser.Provider)); await _userManager.AddToRoleAsync(newUser, "User"); return newUser; } }
五、账号密码登录的规范实现
[HttpPost("password-login")] public async Task<IActionResult> PasswordLogin([FromBody] PasswordLoginRequest request) { var user = await _userManager.FindByEmailAsync(request.Email); if (user == null) return BadRequest("邮箱或密码错误"); var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: false); if (!result.Succeeded) return BadRequest("邮箱或密码错误"); var tokenResponse = await _tokenService.GenerateAccessTokenAsync(user); return Ok(tokenResponse); }
六、核心优势
- 复用Identity内置方法,避免手动操作数据库表,降低出错风险
- 自动生成符合规范的Claims集合,无需手动拼接身份、角色信息
- 统一第三方登录核心逻辑,新增平台只需替换令牌验证部分,业务流程复用
内容的提问来源于stack exchange,提问作者yeyeyerman
相关产品推荐
相关产品推荐

