ASP.NET Core Identity集成非官方OAuth2.0提供商(如Discord)问题咨询
ASP.NET Core Identity 集成 Discord OAuth2.0 实现方案
核心疑问解答
CallbackPath 无需手动创建控制器动作
ASP.NET Core OAuth中间件会自动处理该路径的回调请求,你只需保证此路径与Discord开发者后台配置的重定向URI一致即可,无需自行编写对应Controller Action。OnCreatingTicket 与 GetExternalLoginInfoAsync 的关系
OnCreatingTicket是OAuth中间件获取用户授权后,调用UserInformationEndpoint拉取用户信息的扩展点,可在此将Discord返回的用户标识(如ID、用户名、头像)添加到Claims集合中。SignInManager.GetExternalLoginInfoAsync()是Identity封装的方法,会从OAuth中间件处理后的上下文提取外部登录信息(包含你在OnCreatingTicket中添加的Claims),用于后续创建本地用户或关联已有用户。
完整实现示例
假设你已搭建好ASP.NET Core Identity基础项目,以下是集成Discord OAuth的关键代码:
1. 配置Discord OAuth服务(Program.cs)
builder.Services.AddAuthentication() .AddOAuth("Discord", options => { // 基础身份验证配置 options.ClientId = builder.Configuration["Discord:ClientId"]; options.ClientSecret = builder.Configuration["Discord:ClientSecret"]; options.CallbackPath = "/signin-discord"; // 需与Discord后台重定向URI完全一致 // Discord OAuth端点地址 options.AuthorizationEndpoint = "https://discord.com/api/oauth2/authorize"; options.TokenEndpoint = "https://discord.com/api/oauth2/token"; options.UserInformationEndpoint = "https://discord.com/api/users/@me"; // 申请的权限范围 options.Scope.Add("identify"); options.Scope.Add("email"); // 如需获取邮箱,需在Discord后台开启并申请该权限 // 处理用户信息并添加自定义Claims options.Events.OnCreatingTicket = async context => { // 调用Discord用户信息接口 var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); var userDoc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement; // 添加用户唯一标识 context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userDoc.GetProperty("id").GetString())); // 添加Discord用户名(格式:username#discriminator) context.Identity.AddClaim(new Claim(ClaimTypes.Name, $"{userDoc.GetProperty("username").GetString()}#{userDoc.GetProperty("discriminator").GetString()}")); // 添加邮箱(若已申请email权限) if (userDoc.TryGetProperty("email", out var emailElement)) { context.Identity.AddClaim(new Claim(ClaimTypes.Email, emailElement.GetString())); } // 添加头像链接 var avatarId = userDoc.GetProperty("avatar").GetString(); if (!string.IsNullOrEmpty(avatarId)) { var avatarUrl = $"https://cdn.discordapp.com/avatars/{userDoc.GetProperty("id").GetString()}/{avatarId}.png"; context.Identity.AddClaim(new Claim("AvatarUrl", avatarUrl)); } }; }); // 初始化Identity服务 builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
2. 处理外部登录回调(AccountController.cs)
可复用Identity默认外部登录流程,或自定义控制器,以下是核心逻辑:
public class AccountController : Controller { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager) { _signInManager = signInManager; _userManager = userManager; } // 发起Discord登录跳转 [HttpGet] public IActionResult ExternalLogin(string provider, string returnUrl = null) { var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); return new ChallengeResult(provider, properties); } // 处理Discord登录回调 [HttpGet] public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) { if (remoteError != null) { ModelState.AddModelError(string.Empty, $"外部登录错误: {remoteError}"); return View("Login"); } // 获取外部登录信息 var loginInfo = await _signInManager.GetExternalLoginInfoAsync(); if (loginInfo == null) { return RedirectToAction(nameof(Login)); } // 尝试用外部登录信息登录已有用户 var signInResult = await _signInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (signInResult.Succeeded) { return LocalRedirect(returnUrl ?? "/"); } // 若用户不存在,创建新用户 var newUser = new IdentityUser { UserName = loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? loginInfo.Principal.FindFirstValue(ClaimTypes.Name), Email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email) }; var createResult = await _userManager.CreateAsync(newUser); if (createResult.Succeeded) { // 将外部登录信息关联到新用户 createResult = await _userManager.AddLoginAsync(newUser, loginInfo); if (createResult.Succeeded) { await _signInManager.SignInAsync(newUser, isPersistent: false); return LocalRedirect(returnUrl ?? "/"); } } // 处理用户创建失败的错误 foreach (var error in createResult.Errors) { ModelState.AddModelError(string.Empty, error.Description); } return View("Login"); } }
3. 前端登录页面添加Discord登录按钮(Login.cshtml)
<form asp-action="ExternalLogin" method="post"> <button type="submit" name="provider" value="Discord" class="btn btn-primary"> 用 Discord 登录 </button> </form>
4. 配置文件(appsettings.json)
添加Discord客户端凭证:
{ "Discord": { "ClientId": "你的Discord客户端ID", "ClientSecret": "你的Discord客户端密钥" } }
注意事项
- 确保Discord开发者后台的重定向URI与代码中
CallbackPath一致(本地测试用http://localhost:端口号/signin-discord) - 代码中配置的Scope需与Discord后台申请的权限范围匹配
- 如需获取用户邮箱,必须在Discord开发者后台的OAuth2设置中开启"Require OAuth2 Code Grant"并申请
email权限
内容的提问来源于stack exchange,提问作者iKingNinja
相关产品推荐
相关产品推荐

