OAuth教程--授权

Author Avatar
Sean Yu 7月 13, 2018
  • 在其它设备中阅读本文章

本文是oauth.com上的教程的翻译。(原文地址

本章是对OAuth服务提供商的指导。

授权界面是用户在授予应用程序访问其帐户时看到的页面。以下部分介绍如何构建授权页面,界面中包含哪些组件以及如何最好地向用户显示界面。

在实施OAuth服务时,您正在使开发人员能够构建利用您的平台的应用程序,允许应用程序访问并可能修改私有用户内容,或代表用户行事。因此,您需要确保为用户提供尽可能多的信息以保护其帐户,并确保他们了解应用程序对其帐户执行的操作。

授权请求

客户端会将用户的浏览器定向到授权服务器以开始OAuth流程。客户端可以使用授权码授予类型或隐式授权。除了response_type参数指定的授权类型外,请求还有许多其他参数来指示请求的细节。

OAuth 2.0客户端描述了客户端如何为您的服务构建授权URL。授权服务器第一次看到用户申请此授权请求时,将使用客户端设置的查询参数将用户定向到服务器。此时,授权服务器将需要验证请求并提供授权接口,允许用户批准或拒绝该请求。

请求参数

以下参数用于开始授权请求。例如,如果授权服务器URL是https://authorization-server.com/auth, 客户端将创建如下的URL并将用户的浏览器指向它:

https://authorization-server.com/auth?response_type=code
&client_id=29352735982374239857
&redirect_uri=https://example-app.com/callback
&scope=create+delete
&state=xcoivjuywkdkhvusuye3kch

response_type
response_type将设置为code,表示应用程序希望在成功时收到授权码。

client_id
client_id是应用程序的公共标识符。

redirect_uri (可选的)
redirect_uri不规范所要求的,但你的服务应该需要它。此URL必须与开发人员在创建应用程序时注册的其中一个URL匹配,如果请求不匹配,授权服务器应拒绝该请求。

scope (可选的)
请求可以具有一个或多个scope值,指示应用程序请求的附加访问。授权服务器需要向用户显示请求的scope。

state (推荐的)
state应用程序使用该参数来存储特定于请求的数据和/或防止CSRF攻击。授权服务器必须将未修改的state值返回给应用程序。

授予类型

当客户端应用程序期望授权码作为响应时,授权码授予类型将与密钥和公共客户端一起工作。要启动授权码授予,客户端将使用查询参数response_type=code以及其他所需参数将用户的浏览器定向到授权服务器。

验证授权请求

授权服务器必须首先验证client_id请求中的对应的有效的应用程序。

如果您的服务器允许应用程序注册多个重定向URL,则验证重定向URL有两个步骤。如果请求包含redirect_uri参数,则服务器必须确认它是此应用程序的有效重定向URL。如果redirect_uri请求中没有参数,并且只注册了一个URL,则服务器使用先前注册的重定向URL。否则,如果请求中没有重定向URL,并且没有注册重定向URL,就将会返回一个错误。

如果client_id无效,服务器应立即拒绝请求并向用户显示错误。

无效的重定向网址

如果授权服务器检测到重定向URL有问题,则需要通知用户该问题。由于多种原因,重定向网址可能无效,包括:

  • 缺少重定向URL参数
  • 重定向URL参数无效,例如,如果它是不解析为URL的字符串
  • 重定向URL与应用程序的已注册重定向URL之一不匹配

在这些情况下,授权服务器应向用户显示错误,通知他们问题。服务器不得将用户重定向回应用程序。这避免了所谓的“开放重定向器攻击”。如果已注册重定向URL,则服务器应仅将用户重定向到重定向URL。

其他错误

通过将用户重定向到重定向URL,并使用查询字符串中的错误代码来处理所有其他错误。有关如何响应错误的详细信息,请参阅“授权响应”部分。

如果请求缺少response_type参数,或者该参数的值是除了code和token之外的任何内容,则服务器会返回invalid_request错误。

由于授权服务器可能要求客户端指定它们是公共的还是机密的,因此它可以拒绝不允许的授权请求。例如,如果客户端指定它们是机密客户端,则服务器可以拒绝使用令牌授权类型的请求。拒绝时,请使用错误代码unauthorized_client。

如果存在无法识别的scope值,授权服务器应拒绝该请求。在这种情况下,服务器可以使用invalid_scope错误代码重定向到回调URL 。

授权服务器需要存储此请求的“state”值,以便将其包含在访问令牌响应中。服务器不得修改或对state值包含的内容做出任何假设,因为它纯粹是为了客户端的便利。

用户登录

用户在单击应用程序的“登录”或“连接”按钮后将看到的第一件事是您的授权服务器UI。由授权服务器决定是否在每次访问授权屏幕时要求用户登录,或者让用户登录一段时间。如果授权服务器要在请求之间记住用户,那么它将需要请求用户在将来访问时授权该应用程序。

通常像Twitter或Facebook这样的网站希望他们的用户在大多数时间都是签名的,因此他们为他们的授权屏幕提供了一种方式,通过不要求他们每次登录来为用户提供简化的体验。但是,根据服务以及第三方应用程序的安全要求,可能需要或允许开发人员选择要求用户在每次访问授权屏幕时登录。

在Google的API中,应用程序可以添加prompt=login到授权请求,这会导致授权服务器在显示授权提示之前强制用户再次登录。

在任何情况下,如果用户已退出,或者在您的服务上还没有帐户,则需要为他们提供在此屏幕上登录或创建帐户的方法。

Twitter的授权屏幕的退出视图

因为OAuth 2.0规范中未指定,您可以按照您希望的方式对用户进行身份验证。大多数服务使用传统的用户名/密码登录来验证其用户,但这绝不是解决问题的唯一方法。在企业环境中,常见的技术是使用SAML(一种基于XML的身份验证标准)来利用组织中的现有身份验证机制,同时避免创建另一个用户名/密码数据库。

一旦用户使用授权服务器进行身份验证,它就可以继续处理授权请求并将用户重定向回应用程序。通常,服务器将认为成功登录也意味着用户授权该应用程序。在这种情况下,具有登录提示的授权页面将需要包括描述以下事实的文本:通过登录,用户正在批准该授权请求。这将导致以下用户流程。

登录和未登录的用户流程

如果授权服务器需要通过SAML或其他内部系统对用户进行身份验证,则用户流程将如下所示

用于单独验证服务器的用户流

在此流程中,用户在登录后被定向回授权服务器,在那里他们看到授权请求,就像他们已经登录一样。

授权接口

授权界面是用户在收到来自第三方应用程序的授权请求时将看到的屏幕。由于第三方应用程序会要求用户授予某种级别的访问权限,因此您需要确保用户在授权应用程序权限时做出明智决策所需的所有信息。授权接口通常具有以下组件:

该服务应该易于用户识别,因为他们需要知道他们授予哪些服务访问权限。但是,您确定主页上的网站应与授权界面一致。通常,这通过在屏幕的一定的位置显示应用程序名称和logo,或在整个网站上使用一致的颜色方案来完成。

用户识别

如果用户已登录,则应向用户指明。这可能就像在屏幕的右上角显示他们的名字和照片一样,就像在网站的其他部分一样。

重要的是,用户知道他们当前登录的帐户,以防他们管理多个帐户,以便他们不会错误地授权不同的用户帐户。

申请细节

授权接口应清楚地标识发出请求的应用程序。除了开发人员提供的应用程序名称之外,通常还应该显示网站和应用程序的logo,这是开发人员注册应用程序时收集的信息。我们在客户端注册中详细讨论了这一点。

要求的范围

应该向用户清楚地显示授权请求中提供的范围值。范围值通常是表示特定访问的短字符串,因此应向用户显示更易于阅读的版本。

例如,如果服务将“私有”范围定义为对私有配置文件数据的读取访问权限,则授权服务器应该说“此应用程序将能够查看您的私有配置文件数据”。如果范围明确允许写入访问,也应该在描述中标识,例如“此应用程序将能够编辑您的配置文件数据”。

如果没有范围,但您的服务仍然授予对用户帐户的一些基本访问级别,则应该包含描述应用程序将访问的内容的消息。如果省略范围意味着应用程序获得的唯一内容是用户标识,则可以包含一条消息,以表示“此应用程序希望您登录”。

有关如何在服务中有效使用范围的更多信息,请参阅范围

请求的或有效的生命周期

授权服务器必须决定授权的有效期,访问令牌的持续时间以及刷新令牌的持续时间。

大多数服务不会自动使授权失效,而是希望用户定期查看和撤消对他们不再想要使用的应用的访问权限。但是,默认情况下,某些服务提供有限的令牌生存期,并允许应用程序请求更长的持续时间,或强制用户在授权过期后重新授权应用程序。

无论您对授权的有效期做出何种决定,都应该向用户明确说明应用程序能够代表用户执行多长时间。这可能是一个句子,加单的表明:“这个应用程序将能够访问您的帐户,直到您撤销访问”或者“这个应用程序将能够访问您的帐户,时效为一个星期。”关于令牌生命周期的更多信息请参见访问令牌生命周期

允许否认

最后,授权服务器应该向用户提供两个按钮,以允许或拒绝该请求。如果用户未登录,则应提供登录提示而不是“允许”按钮。

如果用户批准该请求,则授权服务器将生成访问令牌并使用令牌信息重定向到应用程序。如果用户单击“拒绝”,则服务器会重定向到应用程序,并在URL中显示错误代码。

授权响应

根据授权类型,授权服务器将使用授权码或访问令牌进行响应。

授权码响应

如果请求有效并且用户授予授权请求,则授权服务器生成授权码并将用户重定向回应用程序,将代码和先前的“state”值添加到重定向URL。

生成授权码

授权码必须在颁发后不久到期。OAuth 2.0规范建议最长生命周期为10分钟,但实际上,大多数服务设置的到期时间要短得多,大约30-60秒。授权代码本身可以是任意长度,但应记录代码的长度。

因为授权代码是短暂的并且是单次使用的,所以它们是实现自编码的很好的候选者。使用此技术,您可以避免将授权代码存储在数据库中,而是将所有必要的信息编码到代码本身中。您可以使用服务器端环境的内置加密库,也可以使用JSON Web Signature(JWS)等标准。由于此字符串只需要您的授权服务器可以理解,因此不需要使用JWS等标准来实现此字符串。也就是说,如果您没有可以轻松访问的已经可用的加密库,JWS是一个很好的候选者,因为有许多语言的库可用。

需要与授权代码关联的信息如下。

client_id
请求此代码的客户端ID(或其他客户端标识符)

redirect_uri
使用的重定向网址。需要存储,因为访问令牌请求必须包含相同的重定向URL,以便在发出访问令牌时进行验证。

用户信息(User info)
用于标识此授权代码所针对的用户的某种方式,例如用户ID。

到期日期(Expiration Date)
代码需要包含到期日期,以便使它能只持续很短的时间。

唯一ID(Unique ID)
代码需要其自己的某种ID,以便能够检查代码之前是否已被使用过。数据库ID或随机字符串就足够了。

通过创建JWS令牌或生成随机字符串并将关联信息存储在数据库中生成授权码后,您需要将用户重定向到指定的应用程序重定向URL。要添加到重定向URL的查询字符串的参数如下:

code
此参数包含客户端稍后将为访问令牌交换的授权码。

state
如果初始请求包含state参数,则响应还必须包含请求中的确切值。客户端将使用此将此响应与初始请求相关联。

例如,授权服务器通过发送以下HTTP响应来重定向用户。

HTTP/1.1 302 Found
Location: https://oauth2client.com/redirect?code=g0ZGZmNjVmOWI&state=dkZmYxMzE2

隐式授权类型响应

使用隐式授权,授权服务器立即生成访问令牌,并使用令牌和其他参数重定向到回调URL。有关生成访问令牌的详细信息以及响应中所需参数的详细信息,请参阅访问令牌响应

例如,授权服务器通过发送以下HTTP响应来重定向用户(出于显示目的,需要额外的换行符)。

HTTP/1.1 302 Found
Location: https://example-app.com/redirect#access_token=MyMzFjNTk2NTk4ZTYyZGI3
 &state=dkZmYxMzE2
 &token_type=bearer
 &expires_in=86400
错误响应

在两种情况下,授权服务器应直接显示错误消息,而不是将用户重定向到应用程序:如果client_id无效,或者redirect_uri无效。在所有其他情况下,可以将用户重定向到应用程序的重定向URL以及描述错误的查询字符串参数。

重定向到应用程序时,服务器会将以下参数添加到重定向URL:

error
通常是以下列表中的一个ASCII错误代码:

  • invalid_request - 请求缺少参数,包含无效参数,多次包含参数,或者无效。
  • access_denied - 用户或授权服务器拒绝该请求
  • unauthorized_client - 不允许客户端使用此方法请求授权码,例如,如果机密客户端尝试使用隐式授权类型。
  • unsupported_response_type8 - 服务器不支持使用此方法获取授权代码,例如,如果授权服务器从未实现隐式授权类型。
  • invalid_scope - 请求的范围无效或未知。
  • server_error - 服务器可以使用此错误代码重定向,而不是向用户显示500内部服务器错误页面。
  • temporarily_unavailable - 如果服务器正在进行维护或不可用,则可以返回此错误代码,而不是使用503 Service Unavailable状态代码进行响应。

error_description
授权服务器可以可选地包括错误的描述。此参数旨在供开发人员理解错误,而不是要向最终用户显示。除双引号和反斜杠外,此参数的有效字符是ASCII字符集,特别是十六进制代码20-21,23-5B和5D-7E。

error_uri
服务器还可以将URL返回到人类可读的网页,其中包含有关错误的信息。这是为了让开发人员获得有关错误的更多信息,而不是要向最终用户显示。

state
如果请求包含状态参数,则错误响应还必须包含请求中的确切值。客户端可以使用它将此响应与初始请求相关联。

例如,如果用户拒绝授权请求,服务器将构造以下URL并发送HTTP重定向响应(如下所示)(URL中的换行符用于说明目的)。

HTTP/1.1 302 Found
Location: https://example-app.com/redirect?error=access_denied
 &error_description=The+user+denied+the+request
 &error_uri=https%3A%2F%2Foauth2server.com%2Ferror%2Faccess_denied
 &state=wxyz1234

安全考虑因素

以下是构建授权服务器时应考虑的一些已知问题。

除了此处列出的注意事项外,OAuth 2.0线程模型和安全注意事项草案中还提供了更多信息。

网络钓鱼攻击

针对OAuth服务器的一种潜在攻击是网络钓鱼攻击。这是攻击者创建一个看起来与服务授权页面相同的网页的地方,该页面通常包含用户名和密码字段。然后,通过各种手段,攻击者可以诱骗用户访问该页面。除非用户可以检查浏览器的地址栏,否则该页面可能看起来与真正的授权页面相同,并且用户可以输入他们的用户名和密码。

攻击者可以尝试诱骗用户访问伪造服务器的一种方法是将此网络钓鱼页面嵌入到本机应用程序中的嵌入式Web视图中。由于嵌入式Web视图不显示地址栏,因此用户无法直观地确认它们位于合法站点上。遗憾的是,这在移动应用程序中很常见,并且开发人员通常希望通过整个登录过程将用户保留在应用程序中来提供更好的用户体验。某些OAuth提供商鼓励第三方应用程序打开Web浏览器或启动提供程序的本机应用程序,而不是允许它们在Web视图中嵌入授权页面。

对策

确保通过https提供授权服务器以避免DNS欺骗。

授权服务器应该向开发人员介绍网络钓鱼攻击的风险,并且可以采取措施防止该页面嵌入到本机应用程序或iframe中。

应该让用户了解网络钓鱼攻击的危险,并且应该教导用户最佳实践,例如只访问他们信任的应用程序,并定期查看应用程序列表,对不再使用的应用程序授权撤销的访问权限。

该服务可能希望在允许其他用户使用该应用程序之前验证第三方应用程序。Instagram和Dropbox等服务目前都是这样做的,在初始创建应用程序时,该应用程序只能由开发人员或其他白名单用户帐户使用。应用程序提交审批并进行审核后,可以由该服务的整个用户群使用。这使服务有机会检查应用程序如何与服务交互。

点击劫持

在点击劫持攻击中,攻击者创建了一个恶意网站,在该网站中,攻击者网页上方的透明iframe中加载了授权服务器URL。攻击者的网页堆放在iframe下方,并且有一些看似无害的按钮或链接,非常小心地放在授权服务器的确认按钮下面。当用户单击误导性可见按钮时,他们实际上是单击授权页面上的隐藏按钮,从而授予对攻击者应用程序的访问权限。这允许攻击者欺骗用户在他们不知情的情况下授予访问权限。

对策

通过确保授权URL始终直接在本机浏览器中加载,而不是嵌入在iframe中,可以防止这种攻击。较新的浏览器可以让授权服务器设置HTTP标头,X-Frame-Options旧版浏览器可以使用常见的Javascript“框架破坏”技术。

重定向URL操作

攻击者可以使用属于已知正常应用程序的客户端ID构建授权URL,将重定向URL设置为攻击者控制下的URL。如果授权服务器未验证重定向URL,并且攻击者使用“令牌”响应类型,则用户将使用URL中的访问令牌返回到攻击者的应用程序。如果客户端是公共客户端,并且攻击者拦截授权码,则攻击者还可以通过授权码交换访问令牌。

另一个类似的攻击是攻击者可以欺骗用户的DNS,并且注册的重定向不是https URL。这将允许攻击者伪装成有效的重定向URL,并以这种方式窃取访问令牌。

“打开重定向”攻击是指授权服务器不需要重定向URL的完全匹配,而是允许攻击者构建将重定向到攻击者网站的URL。无论这是否最终被用于窃取授权码或访问令牌,这也是一个危险,因为它可以用于发起其他无关的攻击。有关Open Redirect攻击的更多详细信息,请访问https://oauth.net/advisories/2014-1-covert-redirect/

对策

授权服务器必须要求应用程序注册一个或多个重定向URL,并且重定向到先前注册的URL必须完全匹配。

授权服务器还应要求所有重定向URL均为https。由于这有时会给开发人员带来负担,特别是在应用程序运行之前,在应用程序处于“开发阶段”时允许非https重定向URL并且只能由开发人员访问,然后要求在发布应用程序之前,重定向URL应更改为https URL。