我正在为我的 .net 核心站点实现 Google 登录.
I am implementing Google login for my .net core site.
在这段代码中
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
我需要一个 signInManager
这是(通过代码示例):
I need a signInManager
which is (by the code example) this:
private SignInManager<AppUser> signInManager;
我通过构造函数注入它,然后我得到这个错误:
I inject it via the constructor, and then I get this error:
在尝试激活AccountController"时无法解析Microsoft.AspNetCore.Identity.SignInManager1[AppUser]"类型的服务.
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.SignInManager1[AppUser]' while attempting to activate 'AccountController'.
谷歌搜索得知我应该包含这个
Googling learnt that I should include this
services.AddIdentity<AppUser, IdentityRole>()
.AddDefaultTokenProviders();`
但这给了我这个错误:
在尝试激活Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]"时,无法解析Microsoft.AspNetCore.Identity.IUserStore1[AppUser]"类型的服务.
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[AppUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]'.
在那一刻,我得到了添加以下内容的建议:
And at that moment, I get the advice to add this:
.AddEntityFrameworkStores<ApplicationDbContext>()
但是我迷路了,因为为什么 SignInManager
需要一个 IUserStore
,我应该添加一个UserStore
和一个 DBContext
和一个 EntityFramework
存储,我什么时候不会使用它(用于我的 Google 登录)?
But then I'm lost, because why does the SignInManager
need a IUserStore
, and should I add a
UserStore
and a DBContext
and an EntityFramework
store, when I will not be using that (for my Google login)?
所以问题是:我是否也可以在没有 Entityframework 商店的情况下进行 Google 登录?
So the question is: can I also do my Google login without the Entityframework store?
如果你只想用谷歌登录,就不需要SignInManager
、UserManager
或 ASP.NET Core 标识本身.为此,我们首先需要配置身份验证服务.这是相关的代码,我会在后面解释:
If all you want to do is sign-in with Google, there's no need for SignInManager
, UserManager
or ASP.NET Core Identity itself. To achieve this, we first need to configure the Authentication services. Here's the relevant code for this, which I'll explain after:
Startup.cs
services
.AddAuthentication(o =>
{
o.DefaultScheme = "Application";
o.DefaultSignInScheme = "External";
})
.AddCookie("Application")
.AddCookie("External")
.AddGoogle(o =>
{
o.ClientId = ...;
o.ClientSecret = ...;
});
对 AddAuthentication
的调用配置了一个 DefaultScheme
,它最终被用作 Application 方案和 挑战方案.Application 方案在尝试对用户进行身份验证时使用(他们是否已登录?).Challenge 方案在用户未 登录但应用程序希望提供这样做的选项时使用.稍后我将讨论 DefaultSignInScheme
.
The call to AddAuthentication
configures a DefaultScheme
, which ends up being used as both the Application scheme and the Challenge scheme. The Application scheme is used when attempting to authenticate the user (are they signed in?). The Challenge scheme is used when a user is not signed in but the application wants to provide the option to do so. I'll discuss the DefaultSignInScheme
later.
对 AddCookie
的两次调用为 Application
(我们的 Application 方案)和 添加了基于 cookie 的身份验证方案外部
(我们的 SignIn 方案).AddCookie
也可以采用第二个参数,允许配置例如对应cookie的生命周期等
The two calls to AddCookie
add cookie-based authentication schemes for both Application
(our Application scheme) and External
(our SignIn scheme). AddCookie
can also take a second argument, that allows for configuration of e.g. the corresponding cookie's lifetime, etc.
有了这个,质询过程会将用户重定向到 /Account/Login
(默认情况下 - 这也可以通过 cookie 身份验证选项进行配置).这是一个处理挑战过程的控制器实现(再次,我将在后面解释):
With this in place, the challenge process will redirect the user over to /Account/Login
(by default - this can be configured via the cookie authentication options too). Here's a controller implementation that handles the challenge process (again, I'll explain after):
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);
}
}
让我们把它分解成两个动作:
Let's break this down into the two actions:
登录
为了到达 Login
操作,用户将受到挑战.当用户未使用 Application
方案登录但试图访问受 Authorize
属性(或类似属性)保护的页面时,会发生这种情况.根据您的要求,如果用户未登录,我们希望他们使用 Google 登录.为了实现这一点,我们发出了新 挑战,这次是针对 Google
方案.我们使用一个配置了 Google
方案的 ChallengeResult
和一个 RedirectUrl
Google 登录过程完成.如代码所示,我们返回:
In order to arrive at the Login
action, the user will have been challenged. This occurs when the user is not signed in using the Application
scheme but is attempting to access a page protected by the Authorize
attribute (or similar). Per your requirement, if the user is not signed in, we want to sign them in using Google. In order to achieve that, we issue a new challenge, this time for the Google
scheme. We do so using a ChallengeResult
that is configured with the Google
scheme and a RedirectUrl
, which is used for returning to our own application code once the Google sign-in process completes. As the code shows, we return to:
LoginCallback
这就是我们调用 AddAuthentication
中的 DefaultSignInScheme
变得相关的地方.作为 Google 登录过程完成的一部分,DefaultSignInScheme
用于设置一个 cookie,该 cookie 包含一个代表从 Google 返回的用户的 ClaimsPrincipal
(这一切都在后面处理场景).LoginCallback
中的第一行代码获取了这个 ClaimsPrincipal
实例,该实例包含在一个首先检查是否成功的 AuthenticateResult
中.如果到目前为止一切顺利,我们最终会创建一个新的 ClaimsPrincipal
,其中包含我们需要的任何声明(在本例中取自 Google),然后登录该 ClaimsPrincipal
使用 Application
方案.最后,我们重定向到引起我们第一个挑战的页面.
This is where the DefaultSignInScheme
from our call to AddAuthentication
becomes relevant. As part of the Google sign-in process completion, the DefaultSignInScheme
is used for setting a cookie that contains a ClaimsPrincipal
representing the user as returned from Google (this is all handled behind the scenes). The first line of code in LoginCallback
grabs hold of this ClaimsPrincipal
instance, which is wrapped up inside an AuthenticateResult
that is first checked for success. If everything has been successful so far, we end up creating a new ClaimsPrincipal
that contains whatever claims we need (taken from Google in this case) and then signing-in that ClaimsPrincipal
using the Application
scheme. Lastly, we redirect to the page that caused our first challenge.
回应以下评论中的几个后续评论/问题:
In response to a couple of follow-up comments/questions in the comments below:
我是否可以断定 SignInManager
和 UserManager
仅在使用数据库身份验证时使用?
Can I conclude that the
SignInManager
andUserManager
are only used when using authentication with a database?
在某些方面,是的,我认为这是公平的.尽管可以实现内存存储,但如果没有持久性,它并没有多大意义.但是,在您的情况下不使用这些类的真正原因仅仅是因为您不需要本地用户帐户来代表用户.这与坚持是相辅相成的,但值得做出区分.
In some ways, yes, I think that's fair. Although it is possible to implement an in-memory store, it doesn't really make much sense with no persistence. However, the real reason not to use these classes in your situation is simply because you do not need a local user account for representing a user. That goes hand-in-hand with persistence, but it's worth making the distinction.
我在书中读到的代码(用于设置我的谷歌登录)和我读过的所有其他答案有什么不同.
And what very different code from what I read in the book (which I used for setting up my Google login) and all the other answers I've read.
文档和书籍涵盖了最常见的用例,您确实希望存储可以链接到外部帐户(例如 Google 等)的本地用户. 如果您查看 SignInManager
源代码,您会发现它实际上只是位于我上面显示的那种代码之上(例如 这里 和 这里).其他代码可以在默认 UI 中找到(例如 这里)和 AddIdentity
.
The documentation and the books cover the most common use-case, whereby you do want to store local users that can be linked to external accounts such as Google, etc. If you look at the SignInManager
source, you'll see that it's really just sitting on top of the kind of code I've shown above (e.g. here and here). Other code can be found in the Default UI (e.g. here) and in AddIdentity
.
我假设 LoginCallback 被 Google 调用.HttpContext.AuthenticateAsync 是否知道如何检查 Google 发送给我的数据?而且因为它的名字太笼统了,看起来它知道如何为所有外部提供者做到这一点?
I assume the LoginCallback gets called by Google. Does the HttpContext.AuthenticateAsync know how to check the data Google sends me? And as it's name is so generic, it looks like it knows how to do that for all external providers?
此处对 AuthenticateAsync
的调用不了解关于 Google 的任何内容 - 特定于 Google 的处理由对 AddGoogle
的调用配置ConfigureServices
中的 AddAuthentication
.在重定向到 Google 进行登录后,我们实际上回到了应用程序中的 /signin-google
.同样,这要归功于对 AddGoogle
的调用,但该代码实际上只是在 External
方案中发出一个 cookie,该方案存储从 Google 返回的声明,然后重定向到我们配置的 LoginCallback
端点.如果添加对 AddFacebook
的调用,/sigin-facebook
端点将被配置为执行类似操作.对 AuthenticateAsync
的调用实际上只是从由例如创建的 cookie 中补充一个 ClaimsPrincipal
./signin-google
端点,以便检索声明.
The call to AuthenticateAsync
here doesn't know anything about Google - the Google-specific handling is configured by the call to AddGoogle
off of AddAuthentication
in ConfigureServices
. After redirecting to Google for sign-in, we actually come back to /signin-google
in our application. Again, this is handled thanks to the call to AddGoogle
, but that code is really just issuing a cookie in the External
scheme that stores the claims that came back from Google and then redirecting to our LoginCallback
endpoint that we configured. If you add a call to AddFacebook
, a /sigin-facebook
endpoint will be configured to do something similar. The call to AuthenticateAsync
is really just rehydrating a ClaimsPrincipal
from the cookie that was created by e.g. the /signin-google
endpoint, in order to retrieve the claims.
另外值得注意的是,Google/Facebook 登录过程基于 OAuth 2 协议,所以它本身就是通用的.如果您需要的不仅仅是 Google 的支持,您只需针对所需方案发出挑战,而不是像我在示例中所做的那样将其硬编码给 Google.还可以向质询添加其他属性,以便能够确定在到达 LoginCallback
端点时使用了哪个提供程序.
It's also worth noting that the Google/Facebook sign-in process is based on the OAuth 2 protocol, so it's kind of generic in itself. If you were to need support for more than just Google, you would just issue the challenge against the required scheme rather than hardcoding it to Google as I've done in the example. It's also possible to add additional properties to the challenge in order to be able to determine which provider was used when your LoginCallback
endpoint is reached.
我创建了一个 GitHub 存储库,其中包含我构建的完整示例,以便编写此答案这里一个>.
I've created a GitHub repository that contains a complete example that I built in order to write this answer here.
这篇关于如何在没有实体框架提供程序的情况下在 .net 核心中实现谷歌登录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!