OAuth 2如何使用安全令牌来防御重放攻击?

587
据我理解,在OAuth 2中,为了使站点A能够从站点B访问用户的信息,以下事件链将会发生:
  1. 站点A站点B上注册,并获得一个密钥和一个标识符。
  2. 用户告诉站点A要访问站点B时,用户被发送到站点B,并告诉站点B他们确实想授予站点A对特定信息的权限。
  3. 站点B用户重定向回站点A,同时附带一个授权码。
  4. 站点A然后将该授权码与其密钥一起传递回站点B,以换取安全令牌。
  5. 站点A随后将安全令牌与请求捆绑在一起,代表用户站点B提出请求。
就整体安全和加密方面而言,OAuth2是如何工作的?OAuth 2如何通过使用安全令牌来防止重放攻击?

51
OAuth2 简单解释:在此处查看 https://gist.github.com/mziwisky/10079157 - Paolo
4
阅读规范:http://tools.ietf.org/html/rfc6749 你会惊讶于它的易懂程度。同时,它也是正确的,这可能不错。 - Kris Vandermotten
1
这个问题及其(当前的)答案都集中在OAuth 2.0中的一个特定“授权类型”上(即code),但是在OAuth 2.0中还定义了其他授权类型,适用于不同的用例(例如非用户相关的用例)。 - Hans Z.
5
好的,为什么不将“Site B”替换为更容易理解的名称,比如“身份提供者网站(IdProvider Site)”? - Yurii
10个回答

1451

OAuth 2.0在现实生活中的应用:

我上班路过Olaf面包店时,看到了橱窗里最美味的甜甜圈——它滴着巧克力酱,让人垂涎欲滴。于是我走进去,要求买下那个甜甜圈!他说:“好的,价钱是30美元。”

是的,我知道,一只甜甜圈竟然卖30美元!一定很好吃吧!我拿出钱包时,突然听到主厨大喊:“不可以!你不能买这个甜甜圈!” 我问为什么?他说他只接受银行转账。

真的吗?是的,他说得很认真。我当时差点就走了,但是这个甜甜圈叫唤着我:“请享用我,我非常美味……”我怎么能违抗一个甜甜圈的命令呢?于是我答应了。

他递给我一张写有他名字的纸条(是主厨的名字,不是甜甜圈的),上面写着“告诉他们是Olaf派你来的”。上面已经有他的名字了,我不知道为什么还要这样写,但是好吧。

我驱车一个半小时到达我的银行。我把纸条交给了出纳员,告诉她Olaf派我来的。她瞪了我一眼,那种意味深长的眼神,就好像在说:“我会读懂的。”

她拿走我的纸条,要求我出示身份证,并问我打算汇多少钱给他。我告诉她30美元。她做了些记录,递给了我另一张纸条。上面有一堆数字,我猜那是他们追踪订单的方式。

当时我已经饿极了。我匆匆离开银行,在一个半小时之后回到Olaf面前,递给他我的纸条。他拿过去看了一下,说:“我待会儿回来。”

我以为他去拿我的甜甜圈,但是等了30分钟后我开始怀疑。于是我问柜台后面的人:“Olaf去哪里了?”他说:“他去取钱了。”“什么意思?”“他带着纸条去银行了。”

哦,原来 Olaf 拿了银行给我的便条回到银行里取钱。既然他有那张银行给我的便条,银行就知道他是我说的那个人,而且因为我已经和银行交谈过了,所以他们知道只要给他 30 美元。
我一定花了很长时间才明白这一点,因为当我抬头看的时候,Olaf 终于站在我面前递给我甜甜圈了。离开之前,我不得不问:“Olaf,你总是这样卖甜甜圈吗?”。 “不是的,我以前做法不同。”
哦,我走回车子的时候电话响了。我没打算接,可能是我的工作打来解雇我,我的老板真是太讨厌了。此外,我还沉浸在刚刚经历的过程中。
想想看:我能让 Olaf 从我的银行账户里取出 30 美元,而不必向他提供我的账户信息。我也不用担心他会取走太多的钱,因为我已经告诉银行他只能取 30 美元。银行知道他是正确的人,因为他有他们给我的便条。
好吧,当然我宁愿从口袋里掏出 30 美元给他。但是现在他有了那张便条,我可以告诉银行让他每周取 30 美元,然后我只需要去面包店就行了,不必再去银行了。我甚至可以通过电话订购甜甜圈。
当然,我永远不会这样做——那个甜甜圈真难吃。
我想知道这种方法是否有更广泛的应用。他提到这是他的第二种方法,我可以称之为 Olaf 2.0。无论如何,我最好回家了,我得开始找新工作了。但在此之前,我要去镇上那个新地方买一个草莓奶昔,我需要用它把那个甜甜圈的味道冲掉。

47
实际上,Olaf应该能够随时从你的账户中扣除30美元,即使你没有订购任何甜甜圈。有趣的是,这是实际OAuth2.0场景中的主要目标 :) 这确实是一个很好的答案,但是无论谁在阅读此内容,请前往Paolo在问题评论中提到的Git Gist(https://gist.github.com/mziwisky/10079157)。这是一个很好的补充阅读,可以让概念更加清晰明了。 - Samiron
4
很好的回答,但有两个要点需要提出:
  1. 正如@Samiron指出的那样,Olaf将能够随时拿走30美元。
  2. 在实际的OAuth2.0场景中,Olaf在从银行取钱之前无法提供松饼。而在这个例子中,他本可以留下支票,直接把应得的松饼交给Luis。因此,如果我们改变这个例子,让我授权Olaf从我认识的某个第三方获取面团,那么它将更有意义,因为Olaf必须在开始烤松饼之前获得面团(假设Olaf手头上的孤零零一个松饼只是用来展示的!)。
- Ticker23
4
@Prageeth Olaf总是在一个安全的盒子里携带钞票往返银行,如果有人篡改该盒子,它就会泄漏墨水,恢复这张钞票需要很长时间。银行还在客户第一次到访时采集指纹,如果Olaf在烘焙事故中失去了手指,那么他就必须向Luis再次发起银行转账请求,并且银行将根据他脖子上的"Breaking Bread"纹身来识别他。 - Chris
13
我喜欢可爱的回答,就像下一个人一样,当它们的可爱程度有助于使答案更易理解时,那真是太棒了...但归根结底,Stack Overflow 的目标在于教育人们,而这个可爱的故事并没有做到。即使是理解甜甜圈类比需要您已经了解 OAuth2 的工作原理,但该答案的整个重点应该是精确解释它。请考虑编辑此 (顶部) 答案,以实际解释这些概念,而不仅仅是在最后间接地提到它们... 即使这需要放弃几个笑话。 - machineghost
6
使用深思熟虑的角色 - 杂货店老板奥拉夫、银行、甜甜圈爱好者等 - 使我更容易理解故事情节,比起仅使用“地点A”和“地点B”这样的名词来描述某些角色的作用,后者需要我不断地提醒自己。 - OutstandingBill
显示剩余2条评论

139

根据我所了解的,这就是整个流程:

问题中概述的一般流程是正确的。在第2步中,用户X将被认证,并授权站点A访问其在站点B上的信息。在第4步中,该站点将其密钥传回站点B,对自身进行认证,以及授权码,表示它正在请求什么(用户X的访问令牌)。

总体而言,OAuth 2实际上是一个非常简单的安全模型,加密没有直接参与其中。相反,密钥和安全令牌本质上都是密码,整个过程仅由https连接的安全性保护。

OAuth 2没有保护安全令牌或密钥的重放攻击。相反,它完全依赖于站点B对这些项目的负责和不让它们泄漏,以及在传输过程中通过https发送它们(https将保护URL参数)。

授权代码步骤的目的只是为了方便,授权代码本身并不特别敏感。当要求站点B提供用户X的访问令牌时,它为站点A提供了一个公共标识符。仅使用站点B上的用户X用户ID将无法起作用,因为可能有许多未解决的访问令牌正在等待同时交给不同的站点。


30
你忽视了授权码的一个重要功能。为什么不直接返回刷新令牌(你所说的安全令牌),而不是通过额外的步骤交换授权码来获取它?因为截获刷新令牌会导致重放攻击,而授权码只能使用一次。 - Maurice Naftalin
3
好的,@mauricen,那很有道理。但是,既然每个请求都会传递刷新令牌,那么重放攻击不也可以使用刷新令牌来实现吗? - Mr Mikkél
17
授权码通过用户传递,因此(例如)可以作为 cookie 存储(请参见 https://dev59.com/D2855IYBdhLWcg3w75Iv?rq=1)。刷新令牌直接在两个站点之间传递,因此更不容易受到攻击。 - Maurice Naftalin
出于好奇,OAuth 是否返回任何唯一标识符供程序使用?例如,我目前依靠 MAC 地址进行用户标识,但是可以说,MAC 地址不可靠/容易受到欺骗等。如果 OAuth 允许我唯一地识别用户,那么我可能会放弃 MAC 地址标识机制而选择 OAuth。 - theGreenCabbage
1
请注意这个图表:http://tools.ietf.org/html/rfc6749#section-4.1 中没有显示“Secret”,只有客户端标识符(问题中的ID)。为什么“Secret”很重要,为什么它没有包含在RFC中?此外,在问题中还有一个本地状态,建议在初始传输客户端ID(A)时传递,并将重定向回客户端以及授权码一起用于防止XSSF。 - David Williams
我认为关键是Site-A可以重复使用安全令牌,直到它在一段时间后“过期”。就像一个价值的“保持登录会话”的令牌。此外,请注意用户被发送到Site-B并使用其凭据登录,以进行第2步操作。我想您说得对,通常他们只是假定连接是安全的,并且令牌不会泄漏(因为对于第3点,它将重定向回预配置的已知URL,这被认为是安全的)。 - rogerdpack

111

OAuth是一种协议,允许第三方应用在没有您的账号和密码的情况下访问另一个网站上存储的数据。如需更正式的定义,请参考Wiki或规范。

以下是一个使用案例演示:

  1. 我登录LinkedIn并想要连接一些在我的Gmail联系人中的朋友。 LinkedIn支持此功能。它将从Gmail请求安全资源(我的Gmail联系人列表)。 因此,我单击此按钮:
    添加连接

  2. 弹出一个网页,显示Gmail登录页面,当我输入我的账号和密码时:
    添加连接

  3. Gmail随后显示同意页面,我点击“接受”: 添加连接

  4. 现在LinkedIn可以访问我的Gmail联系人: 添加连接

以下是上述示例的流程图:

添加连接

步骤1:LinkedIn从Gmail的授权服务器请求令牌。

步骤2:Gmail授权服务器验证资源所有者并显示同意页面。(如果用户尚未登录Gmail,则需要登录)

步骤3:用户授予LinkedIn访问Gmail数据的请求。

步骤4:Gmail授权服务器用访问令牌回复。

步骤5:LinkedIn使用此访问令牌调用Gmail API。

步骤6:如果访问令牌有效,则Gmail资源服务器返回联系人。(Gmail资源服务器将验证令牌)

您可以在这里获取有关OAuth的更多详细信息。


你的所有图片都不见了。你能否加载它们到 stack.imgur 上吗? - ChrisF
1
这怎么可能是正确的呢?这个过程不是由 LinkedIn 发起的,而是由坐在浏览器前面的用户发起的。但你把它作为第三步了。这就是我不理解的地方。 - hookenz
20
最简单的解释。谢谢,我再也不会买甜甜圈了。 - OverCoder
第四步,LinkedIn 返回一个授权令牌。这必须在第五步中提供,我们将获得访问令牌和刷新令牌,可用于进一步保护资源。 - amesh
@amesh 谢谢,你是对的,那就是授权码流程,在这里我只是简单地陈述了OAuth 2的基本概念。 - Owen Cao
迄今为止最好的答案。 - lolololol ol

25

图1,摘自RFC6750

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+

15

下面是 OAuth 2.0 的工作原理,这篇文章有详细解释。

在此输入图片描述


你能否描述一下OAUTH2,但不使用Facebook或其他第三方,而是使用秘密密钥和TOTP令牌与手机应用程序来保护Web应用程序? - Al Grant
Facebook在这个例子中是授权服务器,它向任何客户端发放访问令牌,以便他们可以访问Facebook API。如果您想保护自己的API,您需要实现自己的授权服务器。然后,您可以决定使用哪种授权类型来获取访问令牌。 请告诉我您需要什么?我会解释。 - Suraj
我正在考虑使用Spring Boot Security进行设置。客户端(手机)和Web应用程序在注册时交换密钥,然后使用Google Authenticator生成基于时间/密钥的代码,在登录时除了密码之外还需输入该代码。 - Al Grant
我的最后一条评论对你有没有更多启示?请查看我的个人资料获取我的 Twitter 信息。 - Al Grant
您可以在注册时获取客户端ID和密钥。然后,手机使用客户端ID向您的Web应用程序(授权服务器)发出登录请求。Web应用程序验证客户端ID,并将OTP发送到手机。手机使用客户端密钥向Web应用程序发出另一个请求,以交换OTP并获取访问令牌。手机使用此访问令牌访问Web应用程序上的受保护资源。我认为这将是给定场景的OAuth2流程。如果有帮助,请告诉我。 - Suraj

12

这是一篇宝贵的资料:

https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2

非常简要的概述:

OAuth 定义了四个角色:

  1. 资源拥有者
  2. 客户端
  3. 资源服务器
  4. 授权服务器

你(资源拥有者)有一个手机。你有几个不同的电子邮件账户,但你想把所有的电子邮件账户都放在一个应用程序中,这样你就不需要不停地切换。因此,你的 GMail(客户端)请求访问(通过 Yahoo 的授权服务器)你的 Yahoo 邮件(资源服务器),以便你可以在 GMail 应用程序中读取两封邮件。

OAuth 存在的原因是为了避免 GMail 存储你的 Yahoo 用户名和密码造成不安全的情况。

输入图像描述


9
另一个答案非常详细,并回答了OP提出的大部分问题。
具体来说,为了回答OP提出的“OAuth 2如何防止使用安全令牌的重放攻击?”这个问题,官方建议有两种附加保护措施来实现OAuth 2:
  1. 令牌通常会有短暂的过期时间 (https://www.rfc-editor.org/rfc/rfc6819#section-5.1.5.3):

令牌的短暂过期时间是防范以下威胁的一种手段:

  • 重放攻击...
  1. 当站点A使用该令牌时,推荐将其放在Authorization请求头字段中而非URL参数中(https://www.rfc-editor.org/rfc/rfc6750):

客户端应该使用带有“Bearer” HTTP授权方案的“Authorization”请求头字段和令牌对进行认证请求。

除非参与浏览器没有访问“Authorization”请求头字段的应用程序上下文,否则不应使用“application/x-www-form-urlencoded”方法。

URI查询参数包含在文档中以记录当前使用情况;由于其安全性缺陷,不建议使用它。


5
这里是OAuth2的最简单解释,适用于所有4种授权类型,即4种不同的流程,应用程序可以在其中获取访问令牌。
相似之处
所有授权类型流程都有两个部分:
1. 获取访问令牌 2. 使用访问令牌
第二部分“使用访问令牌”对所有流程都是相同的。
差异
每种授权类型流程的第一部分“获取访问令牌”都有所不同。
然而,通常“获取访问令牌”部分可以总结为以下5个步骤:
1. 与OAuth提供者(例如Twitter等)预注册您的应用程序(客户端),以获取客户端ID/密钥。 2. 在您的页面上创建一个带有客户端ID和所需范围/权限的社交登录按钮,以便当用户单击时,用户将被重定向到OAuth提供者进行身份验证。 3. OAuth提供者要求用户授予您的应用程序(客户端)权限。 4. OAuth提供者发出代码。 5. 应用程序(客户端)获取访问令牌。
这里是一个并排对比图,展示了每种授权类型的5个步骤之间的不同流程。
这个图表来自https://blog.oauth.io/introduction-oauth2-flow-diagrams/

enter image description here

每种方式的实现难度、安全性和使用情况都不同。根据您的需求和情况,您将不得不使用其中之一。哪一个要用?
客户端凭据:如果您的应用程序仅为单个用户提供服务
资源所有者密码凭据:这只应作为最后一手选择,因为用户必须将其凭据交给应用程序,这意味着应用程序可以执行用户可以执行的所有操作。
授权码:获取用户授权的最佳方法
隐式:如果您的应用程序是移动或单页应用程序
这里有更多关于选择的解释: https://blog.oauth.io/choose-oauth2-flow-grant-types-for-app/

1
说实话,在答案中我没有找到一个回答“OAuth 2如何通过安全令牌防止重放攻击”的问题,这是主要问题之一。
首先,OP描述的访问方案仅适用于OAuth 2.0提供的其中一种流程——授权码授权。还有其他流程。所有流程的共同特点是,成功认证后,客户端会收到访问令牌
如何保护自己免受重放攻击?这是可能的(带有一些保留),但您需要了解,首先,这需要一系列措施(下面描述),其次,您不能百分之百地保护自己免受此类攻击,有时您可以立即停止未经授权的访问尝试,有时如果发生攻击,您只能缩短攻击持续时间。
那么,您需要什么来做到这一点:
  1. 使用签名的JWT作为您的令牌。
  2. 对于访问令牌,请使用非常短的过期时间,我认为10分钟就足够了。
  3. 您的授权服务器必须发出刷新令牌,这通常是标准上可选的。刷新令牌的过期时间不应太长,每种情况应该有不同的解决方法,例如对于网站,我会将其设置为比普通用户会话稍长一些。您还可以在用户空闲时实现会话过期,但这适用于应用程序逻辑,并不是标准提供的(这是一个相当简单的机制,但超出了问题的范围)。
  4. 您必须将已发出的刷新令牌存储在授权服务器数据库中。但是,您不必存储访问令牌数据以利用自包含的JWT。
  5. 建议在会话的生命周期内存储有关刷新令牌的数据,即直到刷新令牌过期的时间(实际上它不会是一个令牌,而是一个家族-下面会详细介绍)。
  6. 采取一般措施防止令牌/会话被盗,它们可能是众所周知的,其中包括以下内容:仅使用安全连接;如果您使用cookie在最终用户端存储令牌,请设置cookie标志以保护它们,更多详细信息;实施防止跨站点请求伪造(CSRF)的保护措施,更多详细信息
  7. 现在最有趣的部分开始了)实现刷新令牌轮换。这意味着每当客户端使用刷新令牌获取新的访问令牌(因为访问令牌已过期),必须发出一个新的刷新令牌以及新的访问令牌,并使旧的刷新令牌失效。它可以只是数据库中的一个标志,表示刷新令牌无效。
  8. 每次授权服务器发出刷新令牌时,除了其他所需/推荐的内容外,还必须添加以下声明:jti具有唯一的令牌ID和任何未分配的公共名称的私有声明,例如具有唯一令牌家族ID(在一个会话内)。例如,刷新令牌1具有jti3c30a712-247b-4091-b692-8c3e92b83bb2fid4eb44450-84e9-4fbc-830e-33935e20f7e6,在发出刷新令牌2而不是刷新令牌1之后,它可能具有新的jtif467cf40-8cd7-485e-8711-b5c657832fc6,但将具有相同的fid4eb44450-84e9-4fbc-830e-33935e20f7e6。您保持整个刷新令牌家族在数据库中,直到最后一个仍然有效的令牌失效,例如,直到它过期为止。*您可以不使用fid声明,然后您将不得不使用关系数据库
    当攻击者窃取令牌/会话并尝试重复使用时会发生什么?有几种情况:
    1. 攻击者在合法用户的请求下使用了该令牌/会话,客户端请求颁发新的访问和刷新令牌。也就是说,攻击者先成功地进行了操作。然后,在下一个合法用户的请求中,客户端将向授权服务器发送无效的刷新令牌(因为攻击者更早地发出了请求,并且合法用户的刷新令牌已失效)。该会话将被作废。
    2. 令牌/会话被合法用户使用,攻击者后来窃取并使用了该令牌/会话。在这种情况下,同样的事情会发生-会话将被作废,我想这是可以理解的。
    3. 在令牌/会话被窃取后,合法用户未发送任何更多的请求,则攻击者会一直访问,直到刷新令牌的绝对过期时间(请参阅第9点)。

    授权服务器不能知道谁是合法用户,谁是攻击者,所以在这种情况下,最后一个(有效的)刷新令牌始终被作废,使会话过期/无效。在此之后,合法用户可以通过输入密码来验证自己的身份,而攻击者则不能。

    了解这是如何工作的,您应选择与您的项目相关的令牌过期值。

    我建议您深入了解相关标准以及OAuth 2.0安全最佳实践。在那里,您还将找到令牌重放预防部分


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接