使用外部 OpenID Connect 提供程序进行 ASP.NET Core 身份验证/授权

Ziz*_*Tai 2 c# asp.net-web-api asp.net-identity asp.net-core identityserver4

我正在学习 ASP.NET Core 中的身份验证/授权,并对在我的场景中应该使用哪些组件感到困惑:我有一个 SPA 前端和一个 ASP.NET Core API 后端。我正在使用仅支持授权代码流的第三方 OpenID Connect 提供商(如 Okta),因此我相信只有后端应该与该外部提供商通信。外部提供者仅处理身份验证,而不处理授权。我们的应用程序需要基于角色的访问控制。

  • 考虑到身份验证已由外部 OpenID Connect 提供商处理,我们是否需要 Identity Server?
  • 我们需要 ASP.NET Core Identity 吗?
  • 在这种情况下我们应该如何进行授权呢?

Dai*_*Dai 8

(我假设“身份服务器”您指的是IdentityServer4甚至IdentityServer3

考虑到身份验证已由外部 OpenID Connect 提供商处理,我们是否需要 Identity Server?

不。

人们在想要自行托管自己的 IdP 时使用 IdentityServer4(当然,当您也自行托管自己的 IdP 时,您仍然可以与 Google、Facebook、Apple 等联合),但是当您使用 IdP-as-如果您使用 Okta 或 Azure AD 等 a-service,则无需运行自己的 IdP。

我们需要 ASP.NET Core Identity 吗?

不。

ASP.NET Core Identity基本上是一个实用工具包和节省时间的库,(表面上......)可以更轻松地实现每个传统 Web 应用程序所需的常用用户管理功能:例如注册、密码重置、自动锁定、电子邮件+电话验证,全部由 ASP.NET Core Identity 为您处理 - 包括通过实体框架的用户数据库。

...但是当您使用 IdP 即服务时,这些相同的功能(注册、忘记密码等)应该是您所付费服务的一部分(因此您可以花费数万美元)花费数美元的开发时间构建一个自托管的 IdentityServer4 IdP - 或者您可以跳过所有这些,每月仅向 Okta 支付数十美元。猜猜您的项目预算经理想要选择哪个选项...

使用 ASP.NET Core Identity 或任何拥有自己的用户数据库表的自托管 IdP,您需要查询自己的数据库以获取用户详细信息(用户名、显示名称等)(尽管通常您会存储当用户验证新会话时,结构化安全令牌(JWT、SAML、ClaimsIdentity/ ClaimsPrincipalAuthenticationTicket等)中的当前用户详细信息,这样您就可以避免每个请求都访问数据库) ...

...但是当您使用非本地 OIDC IdP(如 Okta)时,这些用户详细信息由 Okta 存储和管理,也由客户端保存,然后通过前端通道转发给您,或直接通过后端传递 -渠道; 这些用户详细信息(OIDC 中的“声明”,SAML 中称为“断言”)包含在 2 个单独的令牌中:首先,id_token保存与安全无关的用户配置文件字段,例如显示名称和头像图像 URI,而access_token保存安全声明和范围(例如用户角色成员资格、帐户锁定状态等)。如果您的客户端没有向您发送id_token,那么您始终可以使用 IdP 的用户信息端点自己获取相同的数据(不要忘记在本地缓存它,否则它比每个请求都访问本地数据库更昂贵) 。

SAML 的工作原理与 OIDC 非常相似,只是使用自己的术语来表示几乎相同的概念。顺便说一句,我对 OIDC 和 OAuth2 比 SAML 更熟悉(我对 SAML 的经验为零),但据我所知,SAML 使用单个令牌来实现身份声明和安全性 -索赔断言,而 OIDC 将其分为 和id_tokenaccess_token我对此可能是错的......)

在这种情况下我们应该如何进行授权呢?

使用声明性授权策略,根据 中的特定用户安全声明进行验证/身份验证access_token不要使用id_token进行授权)。

但是,虽然您可以在服务器端代码中使用相同的 C# 属性进行声明性授权,但请注意 ASP.NET Web API 客户端和 ASP.NET MVC 浏览器访问者的操作方式不同:

  • 在 ASP.NET Web API 中,客户端可能是用户交互式智能手机应用程序或桌面命令行程序(类似于 Azure PowerShell),也可能是无头后台工作程序或守护进程。最重要的是:所有这些客户端都具有在本地安全地存储诸如承载令牌之类的秘密的能力,因为它们是通过对其本地磁盘进行某种读/写访问来运行的程序。

    • (虽然 JS SPA 客户端使用 ASP.NET Web API,但它们没有存储机密的能力,因此它们是我稍后讨论的特殊情况)。
    • access_token这些客户端都在 HTTP请求标头中发送它们Authorization: Bearer 3q2+7w==,ASP.NET Core 的内置 JWT authX 功能会将其中的声明access_token与您声明的策略进行比较,并使用它来接受或拒绝 HTTP 请求。
  • 而 ASP.NET MVC 客户端都是使用传统的基于 HTTP cookie 的 authX + 会话的 Web 浏览器(Chrome、Firefox 等)。

    • 浏览器客户端将其access_token(以及可选的)逐字id_token存储在其 ASP.NET 安全 cookie 中 - 或者 ASP.NET 安全 cookie 存储一些对缓存服务器端的持久令牌的简短引用,以防止 cookie 膨胀。
      • 此安全 cookie 由您的 Web 应用程序对称加密,并标记为仅 HTTP,因此即使浏览器页面中的恶意注入脚本(XSS 等)也无法访问和读取这些令牌字符串。
      • 注意:因为access_tokenblobid_token可能非常大(很容易达到多个 KB) ,您将达到浏览器的 cookie 长度限制(4096 字节?),因此许多 Web 应用程序只是私下存储所有令牌(在 Redis 或 memcached 或其他东西中)并仅存储对缓存令牌的引用。
  • 当涉及 JS SPA(Angular 等)时,事情很复杂:客户端表面上是用户的 Web 浏览器(实际上是浏览器中的 JavaScript 代码),因此它将fetch在后台发出基于 的请求(而不是浏览器)前台“顶级”文档请求)access_tokenAuthorization标头中使用 ,而不是使用 cookie (无论如何脚本都无法访问它,因为它仅是 HTTP 且已加密,假设甚至位于 ASP.NET 安全 cookie 中第一名) - 但因为 JavaScript 客户端根本没有任何方法在本地安全地存储秘密(window.localStorage不是私有的),所以 SPA 有自己独立的 OIDC 流:隐式流,但它基本上是不安全的 - 与浏览器的结合禁用跨域 cookie 和 OIDC 前端通道依赖的其他技术基本上意味着 SPA 现在不应该fetch外部 RP(即 SPA 前端的后端 ASP.NET Web API 服务)发出自己的HTTP 请求,而应该由一个简单的 ASP.NET MVC 提供服务,该 MVC 将请求代理到外部 RP,因此 SPA 实际上使用 Cookie,而不是承载令牌 - 这意味着在静态 AWS S3 或 Azure Blob 存储中托管 SPA 的好主意现在已经不复存在了

因此,如果您打算构建 SPA,您确实应该仔细阅读 LeastPrivilege.com 的文章,以便您了解浏览器格局正在如何变化(Dominick Baier 是 IdentityServer 的创建者和维护者之一)。


我想说的是,正确地理解 OIDC 是很困难的- 我使用 IdentityServer4 构建了一个自托管 IdP,虽然几个月后我得到了一些东西,但我仍然很容易地花了一年多的时间才真正理解到底发生了什么。