如何在ASP.NET Core 8中通过LinkedIn OAuth创建并持久化IdentityUser?
解决方案:ASP.NET Core 8 + LinkedIn OAuth + Identity 用户持久化 + JWT
核心逻辑
- 在LinkedIn OAuth的
OnCreatingTicket事件中获取用户信息,查询/创建ApplicationUser并持久化到数据库 - 替换Cookie认证为JWTBearer,认证成功后生成JWT返回给React SPA
- 保留Identity的用户管理能力,满足核心功能对用户数据的依赖
步骤1:修改Program.cs配置
1.1 配置Identity与JWT认证
移除Cookie认证,添加JWTBearer配置,同时禁用Identity默认的邮箱/密码端点:
// ... 其他服务配置 builder.Services.AddAuthorization(); builder.Services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); // 配置Identity,禁用不需要的邮箱/密码相关功能 builder.Services .AddIdentityApiEndpoints<ApplicationUser>(options => { options.User.RequireUniqueEmail = true; }) .AddRoles<IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); // 配置JWT认证 var jwtSettings = builder.Configuration.GetSection("Jwt"); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "LinkedIn"; // 默认使用LinkedIn发起认证挑战 }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings["Issuer"], ValidAudience = jwtSettings["Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"])) }; }) .AddOAuth("LinkedIn", options => { options.ClientId = builder.Configuration["LinkedIn:ClientId"]; options.ClientSecret = builder.Configuration["LinkedIn:ClientSecret"]; options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization"; options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken"; options.CallbackPath = new PathString("/oauth/linkedin-cb"); options.UserInformationEndpoint = "https://api.linkedin.com/v2/userinfo"; options.Scope.Add("email"); options.Scope.Add("profile"); options.Scope.Add("openid"); options.SaveTokens = true; options.Events = new OAuthEvents { OnCreatingTicket = async context => { // 获取LinkedIn用户信息 using var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); using var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); var userInfo = await response.Content.ReadFromJsonAsync<JsonElement>(); // 映射基础Claims context.RunClaimActions(userInfo); // 获取UserManager处理用户持久化 var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>(); var email = userInfo.GetProperty("email").GetString(); var name = userInfo.GetProperty("name").GetString(); var linkedInSub = userInfo.GetProperty("sub").GetString(); // 查询或创建用户 var user = await userManager.FindByEmailAsync(email); if (user == null) { user = new ApplicationUser { UserName = email, Email = email, Name = name, LinkedInSub = linkedInSub // 存储LinkedIn唯一标识,避免重复创建 }; var result = await userManager.CreateAsync(user); if (!result.Succeeded) { throw new InvalidOperationException($"创建用户失败: {string.Join(", ", result.Errors.Select(e => e.Description))}"); } } else { // 更新用户信息(如姓名) if (user.Name != name) { user.Name = name; await userManager.UpdateAsync(user); } } // 生成关联Identity用户的ClaimsIdentity var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); identity.AddClaim(new Claim(ClaimTypes.Name, user.Name)); identity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); context.Principal = new ClaimsPrincipal(identity); }, OnTicketReceived = async context => { var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>(); var user = await userManager.GetUserAsync(context.Principal); if (user == null) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } // 生成JWT Token var jwtSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>().GetSection("Jwt"); var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.Name), new Claim(ClaimTypes.Email, user.Email) }; // 添加用户角色(如果需要) var roles = await userManager.GetRolesAsync(user); claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: jwtSettings["Issuer"], audience: jwtSettings["Audience"], claims: claims, expires: DateTime.Now.AddDays(7), signingCredentials: creds); var tokenString = new JwtSecurityTokenHandler().WriteToken(token); // 跳转前端并携带Token(根据SPA实际地址调整) var redirectUri = $"{builder.Configuration["FrontendUrl"]}/login-callback?token={Uri.EscapeDataString(tokenString)}"; context.Response.Redirect(redirectUri); } }; // 配置Claim映射规则 options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); });
1.2 配置应用管道
// ... app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); // 若不需要Identity默认端点,可注释此行 // app.MapIdentityApi<ApplicationUser>(); app.MapGet("/login", () => { return Results.Challenge( new AuthenticationProperties { RedirectUri = "/oauth/linkedin-cb" }, authenticationSchemes: ["LinkedIn"]); }); app.Run();
步骤2:更新ApplicationUser模型
添加字段存储LinkedIn唯一标识,避免重复创建用户:
public class ApplicationUser : IdentityUser { public string Name { get; set; } public string LinkedInSub { get; set; } // 存储LinkedIn的sub字段,唯一标识用户 }
执行迁移并更新数据库:
Add-Migration AddLinkedInSubToUser Update-Database
步骤3:React前端处理
- 引导用户访问后端
/login端点,触发LinkedIn认证流程 - 在回调页面(如
/login-callback)解析URL中的token,存储到localStorage或sessionStorage - 后续API请求在请求头中携带JWT:
Authorization: Bearer {token}
内容的提问来源于stack exchange,提问作者asd12tgzxvbgt
相关产品推荐
相关产品推荐

