首页 > 解决方案 > 如何在没有实体框架提供程序的情况下在 .net 核心中实现谷歌登录

问题描述

我正在为我的 .net 核心站点实施 Google 登录。

在这段代码中

var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);      
return new ChallengeResult("Google", properties); 

我需要一个signInManager(通过代码示例)这个:

private SignInManager<AppUser> signInManager;

我通过构造函数注入它,然后我得到这个错误:

尝试激活“AccountController”时无法解析“Microsoft.AspNetCore.Identity.SignInManager1[AppUser]”类型的服务。

谷歌搜索得知我应该包括这个

services.AddIdentity<AppUser, IdentityRole>()
    .AddDefaultTokenProviders();`

但这给了我这个错误:

尝试激活“Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]”时,无法解析“Microsoft.AspNetCore.Identity.IUserStore1[AppUser]”类型的服务。

在那一刻,我得到了添加这个的建议:

.AddEntityFrameworkStores<ApplicationDbContext>()

但是后来我迷路了,因为为什么SignInManager需要 a IUserStore,我应该添加 a UserStore和 aDBContext和一个EntityFramework商店,当我不使用它时(用于我的谷歌登录)?

所以问题是:我也可以在没有 Entityframework 商店的情况下进行 Google 登录吗?

标签: c#authenticationasp.net-core.net-coreasp.net-core-mvc

解决方案


SignInManager如果您只想使用 Google 登录,则不需要UserManager或 ASP.NET Core Identity 本身。为此,我们首先需要配置身份验证服务。这是相关的代码,我将在后面解释:

启动.cs

services
    .AddAuthentication(o =>
    {
        o.DefaultScheme = "Application";
        o.DefaultSignInScheme = "External";
    })
    .AddCookie("Application")
    .AddCookie("External")
    .AddGoogle(o =>
    {
        o.ClientId = ...;
        o.ClientSecret = ...;
    });
  • 调用AddAuthentication配置 a DefaultScheme,最终被用作Application方案和Challenge方案。尝试对用户进行身份验证时使用应用程序方案(他们是否已登录?)。当用户未登录但应用程序希望提供这样做的选项时,使用挑战方案。我会在后面讨论。DefaultSignInScheme

  • 为(我们的应用程序方案)和(我们的登录AddCookie方案)添加基于 cookie 的身份验证方案的两个调用。也可以采用第二个参数,允许配置例如相应的 cookie 的生命周期等。ApplicationExternalAddCookie

有了这个,挑战过程会将用户重定向到/Account/Login(默认情况下 - 这也可以通过 cookie 身份验证选项进行配置)。这是一个处理挑战过程的控制器实现(再次,我将在后面解释):

AccountController.cs

public class AccountController : Controller
{
    public IActionResult Login(string returnUrl)
    {
        return new ChallengeResult(
            GoogleDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
            });
    }

    public async Task<IActionResult> LoginCallback(string returnUrl)
    {
        var authenticateResult = await HttpContext.AuthenticateAsync("External");

        if (!authenticateResult.Succeeded)
            return BadRequest(); // TODO: Handle this better.

        var claimsIdentity = new ClaimsIdentity("Application");

        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));

        await HttpContext.SignInAsync(
            "Application",
            new ClaimsPrincipal(claimsIdentity));

        return LocalRedirect(returnUrl);
    }
}

让我们把它分解成两个动作:

  1. Login

    为了达成Login行动,用户将受到挑战。当用户未使用该Application方案登录但试图访问受该Authorize属性(或类似属性)保护的页面时,会发生这种情况。根据您的要求,如果用户未登录,我们希望他们使用 Google 登录。为了实现这一目标,我们提出了一个的挑战,这次是针对该Google计划。我们使用ChallengeResult配置了Google方案的 a 和RedirectUrl用于在 Google 登录过程完成后返回到我们自己的应用程序代码的 a 来执行此操作。如代码所示,我们返回:

  2. LoginCallback

    这就是DefaultSignInScheme我们的调用AddAuthentication变得相关的地方。作为 Google 登录过程完成的一部分,DefaultSignInScheme用于设置 cookie,其中包含ClaimsPrincipal从 Google 返回的代表用户的 cookie(这一切都在幕后处理)。第一行代码LoginCallback抓住了这个ClaimsPrincipal实例,它被包裹在一个AuthenticateResult首先检查成功的对象中。如果到目前为止一切都成功了,我们最终会创建一个新的ClaimsPrincipal,其中包含我们需要的任何声明(在本例中取自谷歌),然后ClaimsPrincipal使用该Application方案登录。最后,我们重定向到引起我们第一个挑战的页面。

回应以下评论中的几个后续评论/问题:

我可以得出结论SignInManagerandUserManager仅在对数据库使用身份验证时使用吗?

在某些方面,是的,我认为这是公平的。尽管可以实现内存存储,但如果没有持久性,它并没有多大意义。但是,在您的情况下不使用这些类的真正原因仅仅是因为您不需要本地用户帐户来代表用户。这与持久性密切相关,但值得做出区分。

和我在书中读到的代码(我用来设置我的谷歌登录)和我读过的所有其他答案有什么不同的代码。

文档和书籍涵盖了最常见的用例,您确实希望存储可以链接到外部帐户(如 Google 等)的本地用户。如果您查看SignInManager源代码,您会发现它实际上只是坐着在我上面显示的那种代码之上(例如这里这里)。其他代码可以在默认 UI(例如这里)和AddIdentity.

我假设 LoginCallback 被谷歌调用。HttpContext.AuthenticateAsync 是否知道如何检查 Google 发送给我的数据?由于它的名称如此通用,它看起来知道如何为所有外部提供者做到这一点?

AuthenticateAsync对这里的调用对谷歌一无所知——谷歌特定的处理是由对AddGoogleoff of AddAuthenticationin的调用配置的ConfigureServices。在重定向到 Google 进行登录后,我们实际上又回到/signin-google了我们的应用程序中。同样,这要归功于对 的调用AddGoogle,但该代码实际上只是在External存储从 Google 返回的声明的方案中发出一个 cookie,然后重定向到LoginCallback我们配置的端点。如果您添加对 的调用AddFacebook/sigin-facebook端点将被配置为执行类似的操作。调用AuthenticateAsync实际上只是ClaimsPrincipal从例如端点创建的 cookie 中重新补水/signin-google,以便检索声明。

还值得注意的是,Google/Facebook 登录过程是基于OAuth 2 协议的,所以它本身是一种通用的。如果您需要的不仅仅是 Google 的支持,您只需针对所需方案发出挑战,而不是像我在示例中所做的那样将其硬编码给 Google。还可以向质询添加其他属性,以便能够确定LoginCallback到达端点时使用了哪个提供程序。


我创建了一个 GitHub 存储库,其中包含我构建的完整示例,以便在此处编写此答案。


推荐阅读