c# - 如何在没有实体框架提供程序的情况下在 .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 登录吗?
解决方案
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
配置 aDefaultScheme
,最终被用作Application方案和Challenge方案。尝试对用户进行身份验证时使用应用程序方案(他们是否已登录?)。当用户未登录但应用程序希望提供这样做的选项时,使用挑战方案。我会在后面讨论。DefaultSignInScheme
为(我们的应用程序方案)和(我们的登录
AddCookie
方案)添加基于 cookie 的身份验证方案的两个调用。也可以采用第二个参数,允许配置例如相应的 cookie 的生命周期等。Application
External
AddCookie
有了这个,挑战过程会将用户重定向到/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);
}
}
让我们把它分解成两个动作:
Login
为了达成
Login
行动,用户将受到挑战。当用户未使用该Application
方案登录但试图访问受该Authorize
属性(或类似属性)保护的页面时,会发生这种情况。根据您的要求,如果用户未登录,我们希望他们使用 Google 登录。为了实现这一目标,我们提出了一个新的挑战,这次是针对该Google
计划。我们使用ChallengeResult
配置了Google
方案的 a 和RedirectUrl
用于在 Google 登录过程完成后返回到我们自己的应用程序代码的 a 来执行此操作。如代码所示,我们返回:LoginCallback
这就是
DefaultSignInScheme
我们的调用AddAuthentication
变得相关的地方。作为 Google 登录过程完成的一部分,DefaultSignInScheme
用于设置 cookie,其中包含ClaimsPrincipal
从 Google 返回的代表用户的 cookie(这一切都在幕后处理)。第一行代码LoginCallback
抓住了这个ClaimsPrincipal
实例,它被包裹在一个AuthenticateResult
首先检查成功的对象中。如果到目前为止一切都成功了,我们最终会创建一个新的ClaimsPrincipal
,其中包含我们需要的任何声明(在本例中取自谷歌),然后ClaimsPrincipal
使用该Application
方案登录。最后,我们重定向到引起我们第一个挑战的页面。
回应以下评论中的几个后续评论/问题:
我可以得出结论
SignInManager
andUserManager
仅在对数据库使用身份验证时使用吗?
在某些方面,是的,我认为这是公平的。尽管可以实现内存存储,但如果没有持久性,它并没有多大意义。但是,在您的情况下不使用这些类的真正原因仅仅是因为您不需要本地用户帐户来代表用户。这与持久性密切相关,但值得做出区分。
和我在书中读到的代码(我用来设置我的谷歌登录)和我读过的所有其他答案有什么不同的代码。
文档和书籍涵盖了最常见的用例,您确实希望存储可以链接到外部帐户(如 Google 等)的本地用户。如果您查看SignInManager
源代码,您会发现它实际上只是坐着在我上面显示的那种代码之上(例如这里和这里)。其他代码可以在默认 UI(例如这里)和AddIdentity
.
我假设 LoginCallback 被谷歌调用。HttpContext.AuthenticateAsync 是否知道如何检查 Google 发送给我的数据?由于它的名称如此通用,它看起来知道如何为所有外部提供者做到这一点?
AuthenticateAsync
对这里的调用对谷歌一无所知——谷歌特定的处理是由对AddGoogle
off of AddAuthentication
in的调用配置的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 存储库,其中包含我构建的完整示例,以便在此处编写此答案。
推荐阅读
- c# - 如何使用 MVC 自定义页面错误管理处理 angularjs HTTP 错误?
- javascript - 所有反应烤面包消息都需要单个块,反应烤面包失败或成功
- python - 登录时请求无法点击检查点
- firebase - 没有模拟本地云功能的身份验证信息
- c# - 自定义从 ASP.NET Core API 返回的 JSON,使用 Statuscode 的值
- c - 只读 txt 文件的字母包含空格和数字以及 c
- http - Go: validate subsequent http requests with authenticated client via certificate
- azure - Azure Active Directory B2C 自定义角色和策略
- java - TCP客户端和服务器之间更好的通信方式
- asp.net-core-mvc - 在 Asp.NET Core 3.0 中注册自定义 ModelBinder