这几天我对 ASP.NET Core 有了一些了解,并想尝试通过 LinkedIn 实现身份验证。
我在网上找到的大多数教程都使用 MVC,这是一个无缝的过程,但我想做一个纯 API。
我已经有一些东西可以工作,但我不确定这是否安全(我遇到了一些我以可能不安全的方式“修复”的问题)。
当我配置身份时,我使用
services.AddIdentityCore<ApplicationUser>()
而不是AddIdentity<>
以避免添加cookie身份验证方案,然后service.AddAuthentication(...).AddJwtBearer(...)
。当我
.AddLinkedIn()
(来自 aspnet-contrib 提供商)时,我遇到了问题。/login/external?provider=LinkedIn
,其(?)唯一的作用是
public IActionResult ExternalLogin(string provider)
{
return Challenge(provider);
}
将用户重定向到提供商登录页面。这是我所做的第一件事,我认为这可能是一个肮脏/不安全的修复,但它有效:我被重定向到 LinkedIn 登录页面,然后重定向到我的回调页面(在本例中:
/login/linkedin?code=
)。
这就是我不明白的事情发生的地方:调用
AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler
,可能基于回调路径,并验证所有内容。它还尝试登录,但这不起作用,因为我尚未配置登录方案(JWT Bearer 仅允许身份验证和质询方案)。AddIdentity<>
时,我得到了一个看起来像这样的回调循环:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "ExternalLogin", controller = "Login"}. Executing action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) with arguments (LinkedIn) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI), returned result Microsoft.AspNetCore.Mvc.ChallengeResult in 0.2266ms.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes (LinkedIn).
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[12]
AuthenticationScheme: LinkedIn was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) in 248.7148ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 805.406ms 302
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
AuthenticationScheme: Identity.External signed in.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 1142.4957ms 302
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn
...
...
...
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/external?provider=LinkedIn
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "ExternalLogin", controller = "Login"}. Executing action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) with arguments (LinkedIn) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method RestAPI.Controllers.LoginController.ExternalLogin (RestAPI), returned result Microsoft.AspNetCore.Mvc.ChallengeResult in 0.0104ms.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes (LinkedIn).
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[12]
AuthenticationScheme: LinkedIn was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action RestAPI.Controllers.LoginController.ExternalLogin (RestAPI) in 2.5893ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 4.3076ms 302
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[10]
AuthenticationScheme: Identity.External signed in.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 716.1161ms 302
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/login/linkedin?code=xxx&state=xxx
warn: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[15]
'.AspNetCore.Correlation.LinkedIn.OH3a8tiReVSVrkJ9eEUA0eA-W6UDfSJRoO7GyoLLNCo' cookie not found.
info: AspNet.Security.OAuth.LinkedIn.LinkedInAuthenticationHandler[4]
Error from RemoteAuthentication: Correlation failed..
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Exception: An error was encountered while handling the remote login. ---> System.Exception: Correlation failed.
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 245.3085ms 500 text/html; charset=utf-8
CookieAuthenticationHandler
将用户重定向到用户的原始URI,在那里他被挑战重定向(我想,仍然不确定为什么。我还尝试检查用户是否在ExternalLogin
操作中进行了身份验证,以避免当用户进入此页面时向用户发起挑战,但情况似乎并非如此)。
为了“修复”这两个问题,我最终在收到新票证时通过在上下文中请求
SkipHandler
来禁用 AuthenticationHandler 中的登录阶段(这是在 AuthenticationHandler 验证 LinkedIn 身份验证成功时完成的) ):
.AddLinkedIn(options =>
{
options.ClientId = Configuration["authentication:linked-in:client-id"];
options.ClientSecret = Configuration["authentication:linked-in:client-secret"];
options.CallbackPath = new Microsoft.AspNetCore.Http.PathString("/login/linkedin");
options.Events.OnTicketReceived = context =>
{
context.HttpContext.User = context.Principal;
context.SkipHandler();
return Task.CompletedTask;
};
});
我还将上下文中的
Principal
声明附加到 HttpContext.User
,因为否则我无权访问 /login/linkedin
操作中的声明,并且我需要访问那里的用户 LinkedIn ID。
/login/linkedin
操作就是使用 UserManager.FindByLoginAsync
方法通过登录提供程序查找用户,如果我找不到用户,则创建它,最终生成一个 JWT,就像我为标准所做的那样(使用密码)用户。/external-login-callback#auth_token=XXX&refresh_token=XXX
),以便 SPA 可以将令牌存储在本地存储中,并使用它来验证后续请求。
与此方法相关的第二个问题是我似乎无法访问 LinkedIn 为用户提供的令牌(它们似乎没有存储在任何地方,或者也许我不知道在哪里查找),所以我无法为此外部登录提供程序创建
IdentityUserToken
(尽管这似乎不是问题)。
所以最后,我确实有一些东西在工作,而且我不认为它有任何问题。
然而,我是 Web API 的新手,我真的不确定这是一种有效/安全的方法。
我的问题是:
access_token
?LinkedInAuthenticationHandler
已经向 LinkedIn API 发出请求以获取用户 ID/电子邮件,此令牌有何用处?希望这是清楚的,我能够得到一些答案,但如果没有,请毫不犹豫地要求精确。
如果您有勇气阅读所有内容(即使您没有!),谢谢您!
与指向下游 asp 的 spa 外部提供商一起使用的任何示例代码。网络核心身份API???