如何验证每个用户可以使用 OAuth 和 OpenID Connect 访问哪些资源?
Posted
技术标签:
【中文标题】如何验证每个用户可以使用 OAuth 和 OpenID Connect 访问哪些资源?【英文标题】:How to verify which resources each user can access with OAuth and OpenID Connect? 【发布时间】:2016-03-26 10:12:49 【问题描述】:假设我们有一些 RESTful API,我们想要公开其资源。最终用户将通过客户端应用程序(如在 Web 浏览器上运行的移动应用程序和基于 javascript 的客户端)使用此 API。
使用 OAuth 2.0,此 RESTful API 将位于资源服务器上,我们将拥有一个授权服务器,客户端应用程序在其上注册。然后,用户将在授权服务器上注册,并且能够代表他们授予这些应用程序访问资源的权限。
因此,当用户访问一个客户端应用程序时,他将被重定向到授权服务器并被提示授予该客户端应用程序的权限。之后颁发访问令牌,客户端可以向资源服务器发出请求。
这一切我都很清楚。只有一个缺失的部分:每个资源的保护可能取决于用户。更准确地说,它可能取决于索赔。我的意思是我们可以有以下情况:
资源 http://resourceserver.com/api/first-resource 应该只能由声明为“ExampleClaim”且值为 123 的用户访问。
资源 http://resourceserver.com/api/second-resource 应该只能由声明为“AnotherClaim”且值为 123 的用户访问。
资源http://resourceserver.com/api/third-resource 应该可供任何用户访问。
当我第一次听说 OAuth 正在处理 ASP.NET WebAPI 时,我是这样处理的:当使用 Authorization: Bearer [token]
标头发送请求时,在服务器端设置了线程主体,我认为这意味着用户已通过 API 进行身份验证。所以我使用[Authorize]
属性来验证用户是否可以访问资源。
在更深入地研究了 OAuth 之后,我发现这是对协议的严重滥用。据我所知,OAuth 授权应用程序而不是用户。据我所知,当使用 Authorization 标头发出请求时,访问令牌不应包含有关用户的信息,而应包含允许应用程序发出请求的信息。
考虑到这一点,在请求中发送 Authorization 标头不会识别用户,也不会说明用户是否可以访问所述资源。
在这种情况下,如何执行这种授权?我的意思是,不是授权执行请求的客户端应用程序,而是授权用户根据他的声明访问资源?我相信这就是 OpenID Connect 及其 ID 令牌的用武之地,但我不确定。如何管理这个?
【问题讨论】:
【参考方案1】:访问令牌不包含用户的声明,但它包含已授予客户端应用程序权限的用户的主题。 “主题”是一个技术术语,它意味着一个唯一的标识符。简单地说,“主题”是您数据库中的用户 ID。
在受保护的资源端点,您将:
-
从请求中提取访问令牌。 (RFC 6750)
从授权服务器获取有关访问令牌的详细信息。 (RFC 7662)
验证访问令牌。验证包括 (a) 访问令牌是否已过期,以及 (b) 访问令牌是否涵盖受保护资源端点所需的范围(权限)。
上述步骤 1 到 3 是针对客户端应用程序的访问控制。 OAuth 2.0 (RFC 6749) 就是为此而生的。有关这些步骤的详细信息,请参阅Authlete(我)的“Protected Resource”。
完成上述步骤后,您将:
-
从访问令牌中提取主题。同样,“主题”是用户的唯一标识符。
从您的数据库中检索用户的声明。
根据需要验证声明。
上面从 4 到 6 的步骤是针对用户的访问控制。 OAuth 2.0 不适合这个。
OpenID Connect 的主要目的是以可验证的方式获得ID token。您可以通过验证附加到 ID 令牌的签名来确认 ID 令牌已由正确的一方颁发。有关签名的详细信息,请参阅 JSON Web 签名 (JWS) (RFC 7515)。
ID 令牌本身并不是保护 Web API 的技术。但是,如果您在 ID 令牌中正确使用 at_hash
声明,则可以将其用于此目的(请参阅 OpenID Connect Core 1.0 中的“3.1.3.6. ID Token”)。但是,在受保护的资源端点上,直接从数据库中获取声明要比解析 ID 令牌容易得多。
**[ 评论的附加答案#1 ]**
在您的用例中,您不需要 ID 令牌。这是因为访问令牌已经包含有关用户主题的信息。在正常情况下,该信息相当于 ID 令牌中 sub
声明的值。
因此,您不需要 ID 令牌来获取用户的主题。看第4步的描述,可以找到“从access token中提取主题。”
**[评论的附加答案#2]**
那么从访问令牌中提取主题并验证声明有什么问题吗?或者这是正确的做事方式?
没有错。例如,假设您定义了一个 Web API,https://api.example.com/profile
,它返回用户的个人资料信息。在正常情况下,这样的 API 会接受访问令牌,然后从访问令牌中提取主题以确定要引用的用户。另一方面,如果 API 没有从访问令牌中提取主题,则必须要求“主题”作为请求参数来确定要引用哪个用户(或要求包含“子”声明的 ID 令牌) .即使在这种情况下,API 也必须检查请求参数指定的主题和访问令牌关联的主题是否相同,否则会成为安全问题。
提取主题后检查声明也是正常的步骤。例如,您可能希望根据用户支付的计划(免费计划、轻量计划、企业计划或其他)来限制服务的功能。在这种情况下,您必须参考plan
声明。当然,只有在从访问令牌中提取主题后才能检查此类声明。
因此,(1) 从访问令牌中提取主题,然后 (2) 验证用户的声明是正常的,甚至是受保护资源端点实现中的典型步骤。
【讨论】:
再次感谢@TakahikoKawasaki 的帮助。我想我现在明白了。最后,我们使用 OAuth 访问令牌来对客户端应用程序进行访问控制:哪个客户端可以访问哪个资源。然后从 OpenID Connect 身份令牌中,我们对用户进行访问控制:我们从标识用户的 ID 令牌中选择主题声明,如果用户具有访问资源所需的声明,则查看数据库。最后,我们没有移动声明,而是将主体移动到身份令牌中,并在授权时验证所需的声明。是这样吗? 为您的评论编辑了答案。 感谢@TakahikoKawasaki 的详细回答。只有一点,您在第 4 步中说过“OAuth 2.0 不适用于此”,我理解这一点,因为 OAuth 的目标是对客户端应用程序的访问控制。那么像这样从访问令牌中提取主题并验证声明有什么问题吗?或者这是正确的做事方式?很抱歉有这么多疑问,我真的不是安全专家,我只是从 OAuth 和 OpenID Connect 开始。 编辑了您的其他问题的答案。 你能帮我解决这个问题吗:***.com/questions/47096113/…【参考方案2】:你是对的,OAuth 是 NOT an authentication protocol 而是一个委托协议。
OpenID Connect 为 OAuth 2.0 的令牌颁发模型添加了两个值得注意的身份结构。
身份令牌 - 从一方交付给另一方 可以启用联合身份 SSO 用户体验
一个标准化的身份属性 API - 客户端可以通过它 检索给定用户所需的身份属性。
ID TOken 可以提供给 userinfo_endpoint 以获取信息并提供用户已通过 OpenID 提供者身份验证的保证级别。
顺便说一句:“子”即仅在授权服务器的上下文中是唯一的。建议如果您存储 sub 也存储类似 iss-sub 的内容。想法是谷歌的 tsmith 可能不是 Twitter 的 tsmith
【讨论】:
以上是关于如何验证每个用户可以使用 OAuth 和 OpenID Connect 访问哪些资源?的主要内容,如果未能解决你的问题,请参考以下文章
如何同时从 oauth 认证两种类型的用户。并且在每个请求标头中都需要两个用户凭据
使用 django-oauth-toolkit 进行用户身份验证
使用 django-oauth-toolkit 进行用户身份验证
如何在 spring-security-oauth 中获取经过身份验证的用户