ASP.NET Core (2.1) Web API:身份和外部登录提供程序

问题描述 投票:0回答:1

这几天我对 ASP.NET Core 有了一些了解,并想尝试通过 LinkedIn 实现身份验证。
我在网上找到的大多数教程都使用 MVC,这是一个无缝的过程,但我想做一个纯 API。
我已经有一些东西可以工作,但我不确定这是否安全(我遇到了一些我以可能不安全的方式“修复”的问题)。


应用背景:

  • 我正在开发一个Web API,具有基于JWT Bearer令牌的授权,这意味着我没有任何基于cookie的身份验证
  • SPA(React 应用程序)将与 API 交互
  • 我正在尝试在服务器上实现大部分外部登录逻辑,以简化 SPA 的开发

当我配置身份时,我使用

services.AddIdentityCore<ApplicationUser>()
而不是
AddIdentity<>
以避免添加cookie身份验证方案,然后
service.AddAuthentication(...).AddJwtBearer(...)

我使用电子邮件/密码进行注册/登录,返回 JWT 令牌并使用它们来保护对 API 方法的访问,效果很好。
到目前为止一切顺利。

当我

.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,就像我为标准所做的那样(使用密码)用户。
最后,我将用户重定向到 SPA 的 URI(类似于:
/external-login-callback#auth_token=XXX&refresh_token=XXX
),以便 SPA 可以将令牌存储在本地存储中,并使用它来验证后续请求。

与此方法相关的第二个问题是我似乎无法访问 LinkedIn 为用户提供的令牌(它们似乎没有存储在任何地方,或者也许我不知道在哪里查找),所以我无法为此外部登录提供程序创建

IdentityUserToken
(尽管这似乎不是问题)。


所以最后,我确实有一些东西在工作,而且我不认为它有任何问题。
然而,我是 Web API 的新手,我真的不确定这是一种有效/安全的方法。


我的问题是:

  1. 首先,外部认证可以由API来完成吗?或者应该由 SPA 专门负责将用户重定向到正确的 URL?
    我看到很多人说请求的不是API而是SPA。
  2. 这样做是否会泄露任何敏感信息?
    由于这样做的方式,攻击者是否有可能伪造用户请求并访问我的应用程序?
  3. 如何从 LinkedIn 获取与用户相关的
    access_token

    鉴于
    LinkedInAuthenticationHandler
    已经向 LinkedIn API 发出请求以获取用户 ID/电子邮件,此令牌有何用处?
    是否需要存储它以验证用户没有从 LinkedIn 允许的服务中删除我的应用程序?

希望这是清楚的,我能够得到一些答案,但如果没有,请毫不犹豫地要求精确。
如果您有勇气阅读所有内容(即使您没有!),谢谢您!

c# asp.net-core oauth-2.0 single-page-application asp.net-core-webapi
1个回答
0
投票

与指向下游 asp 的 spa 外部提供商一起使用的任何示例代码。网络核心身份API???

© www.soinside.com 2019 - 2024. All rights reserved.