ASP.NET核心身份验证/授权与外部OpenID Connect提供程序



我正在ASP中学习身份验证/授权。NET核心,并被我应该在我的场景中使用的组件弄糊涂了:我有一个SPA前端和一个ASP。NET核心API后端。我使用的是只支持授权代码流的第三方OpenID Connect提供商(如Okta),所以我认为只有后端应该与这个外部提供商对话。外部提供程序只处理身份验证,而不处理授权。我们的应用程序需要基于角色的访问控制。

  • 考虑到身份验证已经由外部OpenID Connect提供商处理,我们是否需要Identity Server
  • 我们需要ASP吗。NET核心标识
  • 在这种情况下,我们应该如何处理授权

(我认为"Identity Server"指的是Identity Server4,甚至可能是Identity Server3)

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

否。

当人们想要自托管自己的IdP时,他们会使用IdentityServer4(当然,当你也自托管你自己的IdP时,你仍然可以与谷歌、脸书、苹果等联合),但当你使用像Okta或Azure AD这样的IdP-as-a-service时,你就不需要运行你自己的IdP。

我们需要ASP吗。NET核心标识?

否。

ASP。NET Core Identity基本上是一个实用工具包和节省时间的库,(表面上…)使其更容易实现每个传统web应用程序所需的常见用户管理功能:注册、密码重置、自动锁定、电子邮件+电话验证等都由ASP为您处理。NET核心标识-包括通过实体框架的用户数据库。

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

使用ASP。NET核心标识或任何自托管的IdP,其中您有自己的用户数据库表,您需要查询自己的数据库以获取用户详细信息(用户名、显示名称等)(尽管通常情况下,当用户对新会话进行身份验证时,您会将当前用户详细信息存储在结构化安全令牌(JWT、SAML、ClaimsIdentity/ClaimsPrincipalAuthenticationTicket等)中,这样您就可以避免在每次请求时都访问数据库)。。。

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

SAML的工作原理与OIDC非常相似,只是使用了自己的术语来表示几乎相同的概念。顺便说一句,我对OIDC和OAuth2比SAML更熟悉(我对SAML没有任何经验),但AFAIK SAML对身份声明和安全性都使用一个令牌-

声明断言,而OIDC将其分为id_tokenaccess_token(我可能错了…)

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

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

但是,尽管您可以在服务器端代码中使用相同的C#属性进行声明性授权,但请注意ASP。NET Web API客户端和ASP。NET MVC浏览器访问者的做法不同:

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

    • (虽然JS SPA客户端使用ASP.NET Web API,但它们没有存储机密的能力,因此它们是一种特殊情况,我将在稍后讨论)
    • 这些客户端都在HTTPAuthorization: Bearer 3q2+7w==请求标头中发送其access_token,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_tokenid_tokenBlob可能非常大(很容易达到数千字节),您将达到浏览器的cookie长度限制(4096字节
  • 当涉及到JS SPAs(Angular等)时,事情很复杂:客户端表面上是用户的web浏览器(实际上是浏览器中的JavaScript代码),因此它将在后台使用Authorization标头中的access_token而不是cookie来发出基于fetch的请求(而不是浏览器前台的"顶级"文档请求)(无论如何,脚本都无法访问它,因为它只是HTTP并且是加密的,假设它们一开始就在ASP.NET安全cookie中)-但因为JavaScript客户端根本没有任何方法在本地安全地存储机密(window.localStorage不是私有的),所以SPAs有自己独立的OIDC流:隐式流,但它基本上是不安全的-,结合浏览器禁用交叉源cookie和OIDC的Front Channel所依赖的其他技术,这基本上意味着SPAs现在不应该向外部RP(即SPA前端的后端ASP.NET Web API服务)发出自己的CCD_,因此,SPA实际上使用Cookie,而不是Bearer代币,这意味着在静态AWS S3或Azure Blob存储中托管SPA的想法现在已经过时了。

所以,如果你计划建立一个SPA,你真的应该给予LeastPrivilege.com上的一篇文章读得很好,让你了解浏览器环境是如何变化的(Dominick Baier是IdentityServer的创建者和维护者之一)。


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