如何在没有实体框架提供程序的情况下在.net核心中实现google登录 [英] how to implement google login in .net core without an entityframework provider
问题描述
我正在为.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.IUserStore1 [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?
推荐答案
如果您要做的只是使用Google登录,则不需要SignInManager
,UserManager
或ASP.NET Core Identity本身.为此,我们首先需要配置身份验证服务.这是与此相关的代码,我将在后面解释:
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 方案和 Challenge 方案. Application 方案用于尝试验证用户身份(他们是否登录?). 挑战方案是在用户未 登录但应用程序希望提供这样做的选项时使用的.稍后再讨论DefaultSignInScheme
.The call to
AddAuthentication
configures aDefaultScheme
, 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 theDefaultSignInScheme
later.对
AddCookie
的两个调用为Application
(我们的 Application 方案)和External
(我们的 SignIn )都添加了基于cookie的身份验证方案.方案).AddCookie
也可以采用第二个参数,该参数允许配置例如相应cookie的生存期等.The two calls to
AddCookie
add cookie-based authentication schemes for bothApplication
(our Application scheme) andExternal
(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
为了执行
Login
动作,将对用户进行挑战.当用户未使用Application
方案登录但尝试访问受Authorize
属性(或类似属性)保护的页面时,会发生这种情况.根据您的要求,如果用户未登录,我们希望使用Google进行登录.为了实现这一目标,我们这次针对Google
方案提出了一个 new 挑战.我们使用配置有Google
方案的ChallengeResult
和RedirectUrl
进行此操作,一旦Google登录过程完成,该RedirectUrl
用于返回我们自己的应用程序代码.如代码所示,我们返回到:In order to arrive at the
Login
action, the user will have been challenged. This occurs when the user is not signed in using theApplication
scheme but is attempting to access a page protected by theAuthorize
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 theGoogle
scheme. We do so using aChallengeResult
that is configured with theGoogle
scheme and aRedirectUrl
, 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,其中包含一个ClaimsPrincipal
代表从Google返回的用户(所有操作均在后台进行).LoginCallback
中的第一行代码抓住了该ClaimsPrincipal
实例,该实例包装在AuthenticateResult
中,该实例首先被检查是否成功.如果到目前为止一切顺利,我们最终将创建一个新的ClaimsPrincipal
,其中包含我们需要的所有声明(在本例中为Google提出的声明),然后使用Application
方案登录该ClaimsPrincipal
.最后,我们重定向到导致第一个挑战的页面.This is where the
DefaultSignInScheme
from our call toAddAuthentication
becomes relevant. As part of the Google sign-in process completion, theDefaultSignInScheme
is used for setting a cookie that contains aClaimsPrincipal
representing the user as returned from Google (this is all handled behind the scenes). The first line of code inLoginCallback
grabs hold of thisClaimsPrincipal
instance, which is wrapped up inside anAuthenticateResult
that is first checked for success. If everything has been successful so far, we end up creating a newClaimsPrincipal
that contains whatever claims we need (taken from Google in this case) and then signing-in thatClaimsPrincipal
using theApplication
scheme. Lastly, we redirect to the page that caused our first challenge.
我已经创建了一个GitHub存储库,其中包含一个完整的示例,该示例是我在此处编写的. a>.
I've created a GitHub repository that contains a complete example that I built in order to write this answer here.
针对以下评论中的一些后续评论/问题:
In response to a couple of follow-up comments/questions in the comments below:
我能得出结论,只有在对数据库使用身份验证时才使用
SignInManager
和UserManager
吗?从某些方面来说,是的,我认为这很公平.尽管可以实现内存存储,但没有持久性并没有多大意义.但是,在您的情况下不使用这些类的真正原因仅仅是因为您不需要代表用户的本地用户帐户.这与持久性并驾齐驱,但值得一提.
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.
与我在书中读到的代码(用于设置Google登录名)和我读过的所有其他答案有什么截然不同的代码.
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 inAddIdentity
.我假设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的任何信息-通过在ConfigureServices
中从AddAuthentication
调用AddGoogle
来配置Google特定的处理.重定向到Google进行登录后,实际上我们回到了应用程序中的/signin-google
.同样,这要归功于对AddGoogle
的调用,但是该代码实际上只是在External
方案中发布了一个cookie,该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 toAddGoogle
off ofAddAuthentication
inConfigureServices
. 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 toAddGoogle
, but that code is really just issuing a cookie in theExternal
scheme that stores the claims that came back from Google and then redirecting to ourLoginCallback
endpoint that we configured. If you add a call toAddFacebook
, a/sigin-facebook
endpoint will be configured to do something similar. The call toAuthenticateAsync
is really just rehydrating aClaimsPrincipal
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.这篇关于如何在没有实体框架提供程序的情况下在.net核心中实现google登录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
-