【问题标题】:How should a Facebook user access token be consumed on the server-side?应该如何在服务器端使用 Facebook 用户访问令牌?
【发布时间】:2015-02-02 08:10:49
【问题描述】:

前言

我正在开发几个 Web 服务和一些客户端(Web 应用程序、移动设备等),它们将通过 HTTP(s) 与所述服务交互。我目前的工作项目是为产品设计一个认证和授权解决方案。我已决定利用 Facebook、Google、Microsoft、Twitter 等外部身份提供商进行身份验证。

我正在尝试解决“当请求到达我的服务器时,我如何知道用户是谁以及如何确定?”的问题。下面还有更多问题...

要求

  1. 依靠外部身份来表明我正在与谁打交道(基本上我只关心“userId”)。
  2. 系统应使用基于令牌的身份验证(与例如 cookie 或基本身份验证相反)。

    我相信这是在提供松散耦合的同时跨多个客户端和服务器进行扩展的正确选择。

工作流程

根据我对基于令牌的身份验证的阅读和理解,以下是我想象的工作流程。现在让我们关注网络浏览器中的 Facebook。我的假设是其他外部身份提供者应该具有类似的功能,尽管我还没有确认。

注意,在撰写本文时,我的以下内容基于 Facebook 登录版本 2.2

  1. 客户端: 使用JavaScript SDK 发起登录 Facebook
  2. Facebook:用户验证并批准应用权限(例如访问用户的公开个人资料)
  3. Facebook:向客户端发送包含用户访问令牌、ID 和签名请求的响应
  4. 客户端:将用户访问令牌存储在浏览器会话中 (handled by SDK conveniently)
  5. 客户端:通过在授权标头中发送用户的访问令牌 + 用户 ID(可能在自定义标头中)向我的 Web 服务发出安全资源请求
  6. 服务器:从请求头读取用户访问令牌,并通过向 Facebook 提供的 debug_token 图 API 发送请求来启动验证
  7. Facebook:使用用户访问令牌信息(包含 appId 和 userId)回复服务器
  8. 服务器:通过将 appId 与预期(自身已知)和 userId 与客户端请求发送的内容进行比较来完成令牌验证
  9. 服务器:用请求的资源响应客户端(假设是快乐的授权路径)

我想象步骤 5-9 将重复对服务器的后续请求(而用户的访问令牌是有效的 - 未过期、从 FB 端撤消、应用程序权限更改等)

这里有一个图表可以帮助您完成这些步骤。请理解此系统不是单页应用程序 (SPA)。提到的 Web 服务本质上是向客户端提供 JSON 数据的 API 端点;它们不提供 HTML/JS/CSS(Web 客户端服务器除外)。

问题

  1. 首先,根据我的前言和要求,所描述的方法是否存在明显的差距/坑?

  2. 是否需要/推荐向 Facebook 执行出站请求以验证访问令牌(上述步骤 6-8)每个客户端请求

    我至少知道,我必须验证来自客户端请求的访问令牌。但是,我不知道在第一次之后进行后续验证的推荐方法。如果有典型的模式,我有兴趣了解它们。我了解根据我的要求,它们可能取决于应用程序;但是,我只是不知道要寻找什么。一旦我有了一个基本的想法,我就会进行尽职调查。

    例如,可能的想法:

    • 在第一次验证完成后散列访问令牌 + 用户 ID 对,并将其存储在分布式缓存中(所有 Web 服务器均可访问),有效期等于访问令牌。根据来自客户端的后续请求,对访问令牌 + userId 对进行哈希处理并检查其是否存在于缓存中。如果存在,则请求被授权。否则,请联系 Facebook 图形 API 以确认访问令牌。我假设如果我使用 HTTPS(我会这样做),这种策略可能是可行的。但是,性能比较如何?

    • this StackOverflow question 中接受的答案建议在 Facebook 用户令牌的第一次验证完成后创建自定义访问令牌。然后将自定义令牌发送到客户端以进行后续请求。但是,我想知道这是否比上述解决方案更复杂。这将需要实现我自己的身份提供者(我想避免这种情况,因为我想首先使用外部身份提供者......)。这个建议有什么好处吗?

  3. 上述第 3 步中的响应中是否存在signedRequest 字段(提到here),相当于“游戏画布登录”流程中的签名请求参数here

    它们似乎被暗示为等效,因为前者在文档中链接到后者。然而,令我惊讶的是,游戏页面上提到的验证策略并没有在网络文档的“手动构建登录流程”page 中提及。

  4. 如果 #3 的答案是“是”,是否可以使用相同的身份确认策略来解码签名并与服务器端预期使用的策略进行比较?

    我想知道是否可以利用它而不是对 debug_token 图 API 进行出站调用(上面的步骤 #6)来确认推荐的访问令牌here

    当然,为了在服务器端进行比较,签名的请求部分需要与请求一起发送到服务器(上面的第 5 步)。除了在不牺牲安全性的情况下的可行性之外,我想知道与拨打电话相比性能如何。

  5. 当我这样做时,例如,在什么场景/出于什么目的,您会将用户的访问令牌持久保存到数据库中吗? 我没有看到需要这样做的场景,但是,我可能忽略了一些东西。我很好奇一些常见的场景可能会引发一些想法。

谢谢!

【问题讨论】:

标签: facebook security authentication facebook-access-token facebook-authentication


【解决方案1】:

根据您的描述,我建议使用

中所述的服务器端登录流程

以便令牌已经在您的服务器上,并且不需要从客户端传递。如果您使用的是非加密连接,则可能存在安全风险(例如,对于中间人攻击)。

步骤如下:

(1) 登录

您需要在scope 参数中指定要从用户那里收集的权限。请求可以通过普通链接触发:

GET https://www.facebook.com/dialog/oauth?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &response_type=code
   &scope={permission_list}

(2)确认身份

GET https://graph.facebook.com/oauth/access_token?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &client_secret={app-secret}
   &code={code-parameter}

(3) 检查访问令牌

您可以通过

检查您在问题中已经说过的令牌
GET /debug_token?input_token={token-to-inspect}
    &access_token={app-token-or-admin-token}

这应该只在服务器端完成,否则你会让你的应用访问令牌对最终用户可见(不是一个好主意!)。

(4) 扩展访问令牌

一旦获得(短期)令牌,您就可以调用以扩展令牌,如

中所述

如下:

GET /oauth/access_token?grant_type=fb_exchange_token
    &client_id={app-id}
    &client_secret={app-secret}
    &fb_exchange_token={short-lived-token}

(5) 访问令牌的存储

关于在服务器上存储令牌,FB 建议这样做:

(6) 处理过期的访问令牌

如果令牌已过期,FB 不会通知您(并且如果您在拨打电话之前没有保存过期日期并将其与当前时间戳进行比较),您可能会收到来自 FB 的错误消息,如果令牌无效(最多 60 天后)。错误代码将是190:

{
  "error": {
    "message": "Error validating access token: Session has expired at unix 
                time SOME_TIME. The current unix time is SOME_TIME.", 
    "type": "OAuthException", 
    "code": 190
  }
}

如果访问令牌无效,解决方案是让此人再次登录,此时您将能够再次代表他们进行 API 调用。您的应用为新用户使用的登录流程应决定您需要采用哪种方法。

【讨论】:

  • 感谢您花时间回复,托比。请原谅我的简短,但 cmets 是有限的。读后感想: 1. 有几个具体问题没有解决。 2. 您建议的代码流如何用于验证客户端对 Web 服务的请求?你能解释一下客户端-服务器交互吗?正如您现在的答案所示,其中没有任何内容涉及客户。也许我最初的问题并不清楚,但我觉得你误解了“服务器”实体在工作流程步骤中的作用。我已经编辑了问题以澄清。对于此处的任何混淆,我们深表歉意。
  • access_token={app-token-or-admin-token} 我正在为此使用应用程序 ID,它显示“消息”:“无效的 OAuth 访问令牌。”,用于检查访问令牌?
  • 我悬赏这个问题,希望有人能填补你答案中的一些空白。但他们没有,你的仍然是最全面的,所以加分!
  • 谢谢@DuncanJones 您还有哪些问题?也许我可以帮忙...
  • 您是否建议将用户信息(例如姓名、电子邮件、电话)存储在数据库中以供下次登录?
【解决方案2】:
  1. 我没有看到任何明显的漏洞/坑洼,但我不是安全专家。
  2. 一旦您的服务器验证了给定的令牌(第 8 步),如您所说:

此 StackOverflow 问题中接受的答案建议在 Facebook 用户令牌的第一次验证完成后创建自定义访问令牌。然后将自定义令牌发送到客户端以进行后续请求。但是,我想知道这是否比上述解决方案更复杂。这将需要实现我自己的身份提供者(我想避免这种情况,因为我想首先使用外部身份提供者......)。这个建议有什么好处吗?

恕我直言是要走的路。我会使用https://jwt.io/,它允许您使用密钥对值(例如 userId)进行编码。 然后您的客户端将此令牌附加到每个请求。因此,您无需第三方即可验证请求(您也不需要数据库查询)。这里的好处是无需将令牌存储在您的数据库中。

您可以在令牌上定义到期日期,以在您需要时强制客户端再次与第三方进行身份验证。

  1. 假设您希望服务器能够在没有客户端交互的情况下执行某些操作。例如:Open graph stories。在这种情况下,因为您需要以用户的名义发布某些内容,所以您需要存储在数据库中的访问令牌。

(对于第 3 和第 4 问题我无能为力,抱歉)。

【讨论】:

    【解决方案3】:

    Facebook 的问题是他们没有在 Oauth (https://blog.runscope.com/posts/understanding-oauth-2-and-openid-connect) 之上使用 OpenId 连接。
    从而导致他们提供 Oauth 身份验证的自定义方式。

    Oauth2 与 OpenId 连接身份服务通常提供颁发者端点,您可以在其中找到 jwk 的 URL(通过附加“.well-known/openid-configuration”),可用于验证 JWT 令牌及其内容是否由相同的身份服务。 (即访问令牌源自为您提供 jwk 的同一服务)

    例如一些已知的openid连接身份提供者:
    https://accounts.google.com/.well-known/openid-configuration
    https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
    (顺便说一句,Attlasian 只提供这两个服务来执行外部登录并非巧合)

    现在正如您所提到的,您需要支持多个 oauth 提供者,并且因为像 Facebook 一样,并非所有提供者都使用相同的 oauth 配置(它们使用不同的 JWT 属性名称、toke 验证方法等。(Openid 连接试图统一这个过程) )我建议您使用一些中间件身份提供者,例如Oauth0(服务不是协议)或Keycloak。这些可以与外部身份提供者(您提到的社交页面)一起使用,还可以为您提供自定义用户存储。

    优点是它们将身份验证过程统一在一种类型下(例如,两者都支持 openid 连接)。而当使用多个 oauth 提供者和不统一的身份验证工作流程时,您最终会得到冗余的实现,并且需要将不同的信息合并到一种类型下(这基本上是上述中间件身份提供者为您解决的问题)。

    因此,如果您在应用程序中仅使用 Facebook 作为身份提供者,那么请直接为 Facebook Oauth 工作流程实施。但是对于多个身份提供者(在创建公共服务时几乎总是这样),您应该坚持提到的解决方法或找到另一个解决方法(或者可能等到所有社交服务都支持 Openid 连接,他们可能不会这样做)。

    【讨论】:

      【解决方案4】:

      可能会有希望。今年,Facebook 宣布了“有限登录”功能,如果他们将其添加到他们的 javascript sdks 中肯定会让我的生活更轻松:

      https://developers.facebook.com/blog/post/2021/04/12/announcing-expanded-functionality-limited-login/

      在撰写本文时,我只能找到对 iOS 和 Unity SDK 的参考,但它似乎确实返回了一个正常的 JWT,这正是我想要的!

      【讨论】:

        猜你喜欢
        • 2013-05-07
        • 1970-01-01
        • 1970-01-01
        • 2012-02-22
        • 1970-01-01
        • 2014-12-02
        • 1970-01-01
        • 2017-07-23
        • 2011-07-21
        相关资源
        最近更新 更多