1

请考虑 program.cs 中的以下代码:

var builder = WebApplication.CreateBuilder(args);

//omit some builder settings

builder.Services.AddAuthentication(options =>
{
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    options.LoginPath = "/signin";
    options.LogoutPath = "/signout";
})
.AddOAuth("Discord", options =>
{
    options.AuthorizationEndpoint = "https://discord.com/api/oauth2/authorize";
    options.Scope.Add("identify");
    options.Scope.Add("email");

    options.CallbackPath = new PathString("/Account/LoginCallback");

    options.ClientId = "some id";
    options.ClientSecret = "some secret";

    options.TokenEndpoint = "https://discord.com/api/oauth2/token";
    options.UserInformationEndpoint = "https://discord.com/api/users/@me";

    options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
    options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
    options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
    options.ClaimActions.MapCustomJson("urn:discord:avatar:url", user =>
        string.Format(
            CultureInfo.InvariantCulture,
            "https://cdn.discordapp.com/avatars/{0}/{1}.{2}",
            user.GetString("id"),
            user.GetString("avatar"),
            user.GetString("avatar")!.StartsWith("a_") ? "gif" : "png"));

    options.AccessDeniedPath = "/Account/LoginFailed";

    options.Events = new OAuthEvents
    {
        OnCreatingTicket = async context =>
        {
            var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

            var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);

            var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;

            context.RunClaimActions(user);
        }
    };
});

以下是我的帐户控制器内:

[HttpGet("~/signin")]
public async Task<IActionResult> SignIn() => View("SignIn", await HttpContext.GetExternalProvidersAsync());

[HttpPost("~/signin")]
public async Task<IActionResult> SignIn([FromForm] string provider)
{
    if (string.IsNullOrWhiteSpace(provider))
    {
        return BadRequest();
    }

    if (!await HttpContext.IsProviderSupportedAsync(provider))
    {
        return BadRequest();
    }

    return Challenge(new AuthenticationProperties {RedirectUri = "/Account/LoginCallback" }, provider);
}

[HttpGet("~/signout")]
[HttpPost("~/signout")]
public IActionResult SignOutCurrentUser()
{
    return SignOut(new AuthenticationProperties {RedirectUri = "/"},
        CookieAuthenticationDefaults.AuthenticationScheme);
}

[HttpGet]
public IActionResult LoginCallback()
{
    return RedirectToAction("Index", "Home");
}

上面的代码有效。会发生以下情况:

  1. 我点击 LoginViaDiscord 按钮
  2. 调用 SignIn post 方法
  3. 我被重定向到不和谐
  4. 我通过不和谐登录
  5. 我被重定向回 LoginCallback

当我查看网站时,我已经完全登录。问题是我跳过了记录用户并创建帐户的整个数据库步骤。

当我使用 MVC 5 进行类似的身份验证时,我将有一个回调操作方法,并且该回调方法将完成所有设置。创建用户,围绕用户执行逻辑,然后登录。但是,使用此代码,我已经完全登录,并且没有涉及数据库步骤。

我的问题是当挑战返回到 LoginCallback 时如何正确完成与上述相同但不登录的操作,以及如何从 LoginCallback 操作中的不和谐中检索信息?我想在允许用户登录之前对用户执行一些逻辑,并且我还想确保他们的帐户是在数据库中创建的。

4

1 回答 1

1

我的第一个答案是错误的。

您应该在通话中使用该options.Events属性。AddOAuth

财产 描述
拒绝访问 当远程服务器返回拒绝访问错误时调用。
OnCreatingTicket 获取或设置在调用 CreatingTicket 方法时调用的函数。
OnRedirectToAuthorizationEndpoint 获取或设置调用 RedirectToAuthorizationEndpoint 方法时调用的委托。
远程失败 发生远程故障时调用。
OnTicketReceived 在收到远程票证后调用。

最有趣的是OnTicketReceived

在这里您可以检查用户和禁止登录等...

OnTicketReceived = async context =>
{
    ClaimsIdentity? identity = context.Principal!.Identity as ClaimsIdentity;
    string? userId = identity!.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
    //TODO make some logic here. For exapmle:
    string[] allowedUsers = new[] { "931350938345169620", "93123250938345169620", "896089335026528327" };
    if (allowedUsers.Contains(userId))
    {
        context.Success();
    }
    else
    {
        //Anonimously accessible page
        context.Response.Redirect("/Home/AccessDenied");
        context.Fail("You are not allowed");
        context.HandleResponse();
    }
},

UPD1,UPD2:

如果您想将逻辑移动到支持依赖注入的单独类中,那么您可以执行以下操作:


public interface ITicketManager
{
    Task HandleTicket(TicketReceivedContext context);
}

public class TicketManager : ITicketManager
{
    public TicketManager(/*Inject here anything you want*/)
    {
        
    }
    
    public async Task HandleTicket(TicketReceivedContext context)
    {
        ClaimsIdentity? identity = context.Principal!.Identity as ClaimsIdentity;
        string? userId = identity!.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
        //TODO make some logic here. For exapmle:
        string[] disallowedUsers = new[] { "9313509382345169620", "931232509383451696220", "8960892335026528327" };
        if (!disallowedUsers.Contains(userId))
        {
            context.Success();
        }
        else
        {
            //Anonimously accessible page
            context.Response.Redirect("/Home/AccessDenied");
            context.Fail("You are not allowed");
            context.HandleResponse();
        }
    }
}

然后在 DI 中设置,并从options.Events

builder.Services.AddScoped<ITicketManager, TicketManager>();

...

builder.Services.AddAuthentication(options =>
    {
        ...
    })
    .AddCookie(options =>
    {
        ...
    })
    .AddOAuth("Discord", options =>
    {
        ...
       
        options.Events = new OAuthEvents
        {
            ...
            
            OnTicketReceived = async context =>
            {
                ITicketManager manager = 
                    context.HttpContext.RequestServices
                        .GetRequiredService<ITicketManager>();
                await manager.HandleTicket(context);
            },
        };
    });
于 2022-01-14T00:38:47.150 回答