You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Identity集成非官方OAuth2.0提供商(如Discord)问题咨询

ASP.NET Core Identity 集成 Discord OAuth2.0 实现方案

核心疑问解答

  1. CallbackPath 无需手动创建控制器动作
    ASP.NET Core OAuth中间件会自动处理该路径的回调请求,你只需保证此路径与Discord开发者后台配置的重定向URI一致即可,无需自行编写对应Controller Action。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 06:43:14