如何在服务器端使用 Facebook 用户访问令牌?

前言

我正在开发几个网络服务和一些客户端(网络应用程序,手机等) ,它们将通过 HTTP 与上述服务接口。我当前的工作项是为产品设计身份验证和授权解决方案。我已经决定利用外部身份提供商,如 Facebook、谷歌、微软、 Twitter 等进行身份验证。

I'm trying to solve the problem of, "when a request comes to my server, how do I know who the user is and how can I be sure?". More questions below as well...

规定

  1. 依靠外部标识来指示我在与谁打交道(我所关心的基本上就是‘ userId’)。
  2. 系统应该使用基于令牌的身份验证(例如,与 cookie 或基本身份验证相反)。

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

工作流程

基于对基于令牌的身份验证的理解和阅读,下面是我对工作流的设想。现在让我们把注意力集中在 网页浏览器中的 Facebook上。我的假设是,其他外部身份提供程序应该具有类似的功能,尽管我还没有确认。

注意,在写这篇文章的时候,我是根据 Facebook 登录版本2.2写的

  1. 客户端: 使用 < a href = “ https://developers.Facebook.com/docs/Facebook-login/login-flow-for-web/v2.2”rel = “ noReferrer”> JavaScript SDK 初始化登录 Facebook
  2. Facebook: 用户验证和批准应用程序权限(例如访问用户的公共配置文件)
  3. Facebook: 向包含用户访问令牌、 ID 和已签署请求的客户端发送响应
  4. 客户端: 在浏览器会话(方便 SDK 处理)中存储用户访问令牌
  5. Client: 通过在授权标头 + 用户 ID (可能是自定义标头)中发送用户的访问令牌,向我的 Web 服务请求安全资源
  6. 服务器: 从请求头读取用户访问令牌,并通过向 Facebook 提供的 debug _ token 图形 API 发送请求来启动验证
  7. Facebook: 使用用户访问令牌信息(包含 appId 和 userId)回应服务器
  8. 服务器: 通过比较 appId 和预期值(已知)以及 userId 和客户端请求中发送的内容来完成令牌的验证
  9. 服务器: 使用请求的资源响应客户端(假设愉快的授权路径)

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

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

Workflow diagram

问题

  1. 首先,根据我的前言和要求,描述的方法是否有明显的缺口/陷阱?

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

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

    例如,可能的想法:

    • 在第一次验证完成后散列访问令牌 + userId 对,并将其存储在一个分布式缓存中(所有 Web 服务器都可以访问) ,期限等于访问令牌。在来自客户机的后续请求中,散列访问令牌 + userId 对并检查它在缓存中的存在。如果存在,则授权请求。否则,请联系 Facebook 图形 API 来确认访问令牌。我假设如果我使用 HTTPS (我将会使用 HTTPS) ,这种策略可能是可行的。然而,性能比较如何?

    • 这个 StackOverflow 问题中接受的答案建议在 Facebook 用户令牌的第一次验证完成后创建一个自定义访问令牌。然后,自定义令牌将被发送到客户端以便进行后续请求。然而,我想知道这是否比上面的解决方案更复杂。这将需要实现我自己的标识提供程序(这是我想避免的,因为我首先想使用外部标识提供程序...)。这个建议有什么价值吗?

  3. 在上面步骤 # 3(提到的 给你)中的响应中是否存在 signedRequest 字段,它是否等同于“ Games 画布登录”流中的已签名请求参数 给你

    由于前者与后者在文档中有联系,因此它们似乎被暗示为等同的。然而,我很惊讶游戏页面中提到的验证策略在网络文档的“手动构建登录流程”呼叫中没有提到。

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

    Decode & compare from FB docs

    我想知道是否可以利用这一点,而不是对 debug _ token 图形 API (步骤 # 6)进行出站调用,以确认访问令牌是否符合建议的 给你:

    debug_token graph API from FB docs

    当然,为了在服务器端进行比较,需要将已签名的请求部分与请求一起发送到服务器(步骤 # 5)。除了不牺牲安全性的可行性之外,我想知道与打出站呼叫相比,性能如何。

  5. 当我这样做的时候,在什么情况下/出于什么目的,例如,您会将用户的访问令牌持久化到数据库吗? 我不认为我需要这样做,但是,我可能忽略了一些事情。我很好奇,一些常见的场景会不会激发一些想法。

谢谢!

20981 次浏览

根据您的描述,我建议使用如下所述的服务器端登录流

这样令牌就已经在服务器上了,不需要从客户端传递。如果您使用的是非加密连接,这可能会带来安全风险(例如,中间人攻击)。

具体步骤是:

(一)登入

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

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

你看

(二)确认身份

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}

This should only be done server-side, because otherwise you'd make you app access token visible to end users (not a good idea!).

你看

(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 不会通知您令牌是否已过期(如果您没有保存过期日期并在打电话之前将其与当前时间戳进行比较) ,因此如果令牌无效(在 max 之后) ,您可能会从 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 调用。应用程序为新用户使用的登录流程应该决定您需要采用哪种方法。

  1. 我没有看到任何明显的缺口或陷阱,但我不是一个安全专家。
  2. 一旦您的服务器验证了给定的令牌(步骤8) ,如您所说:

在这个 StackOverflow 问题中,公认的答案是建议在 Facebook 用户令牌的第一次验证完成之后创建一个自定义访问令牌。然后,自定义令牌将被发送到客户端以便进行后续请求。然而,我想知道这是否比上面的解决方案更复杂。这将需要实现我自己的标识提供程序(这是我想避免的,因为我首先想使用外部标识提供程序...)。这个建议有什么价值吗?

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

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

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

(我不能帮助3和4个问题,对不起)。

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

具有 OpenId 连接标识服务的 Oauth2通常提供发行者端点,您可以在其中找到 URL (通过附加”。) ,可用于验证 JWT 令牌及其内容是否由相同的标识服务签名。(即访问令牌来自向您提供 jwk 的相同服务)

例如,一些已知的 openid 连接标识提供程序:
Https://accounts.google.com/.well-known/openid-configuration
Https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
(顺便说一下,阿特拉西亚人只提供这两种服务来执行外部登录并非巧合)

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

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

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

There may be hope.. This year, Facebook have announced a "limited login" feature, which, if they were to add to their javascript sdks would certainly make my life easier:

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

在写这篇文章的时候,我只能找到 iOS 和 Unity SDK 的参考资料,但是它似乎返回了一个正常的 JWT,这就是我想要的!