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

时间:2014-12-04 12:31:04

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

前言

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

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

要求

  1. 依靠外部身份来表明我与谁打交道(' userId'基本上就是我所关心的)。
  2. 系统应使用基于令牌的身份验证(而不是Cookie或基本身份验证)。

    我认为这是扩展多个客户端和服务器同时提供松散耦合的正确选择。

  3. 工作流

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

    请注意,截至撰写时,我的基础是Facebook登录版本2.2

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

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

      Workflow diagram

      问题

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

      2. 是否正在向Facebook执行出站请求以验证访问令牌(上面的步骤6-8)每个客户请求是否需要/建议?

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

        例如,可能的想法:

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

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

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

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

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

        Decode & compare from FB docs

        我想知道是否可以利用这一点而不是对debug_token图形API进行出站调用(上面的步骤#6),以确认访问令牌为here

        debug_token graph API from FB docs

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

      5. 虽然我在这方面,但在什么情况/用于什么目的,你会将用户的访问令牌持久存储到数据库中吗? 我没有看到我需要这样做的场景,但是,我可能会忽视某些事情。我很好奇是一些常见的场景可能会引发一些想法。

      6. 谢谢!

4 个答案:

答案 0 :(得分:21)

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

中所述

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

步骤如下:

(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调用。您的应用用于新用户的登录流程应确定您需要采用哪种方法。

答案 1 :(得分:1)

  1. 我不喜欢'看到任何明显的差距/坑落,但我不是安全专家。
  2. 一旦您的服务器验证了给定令牌(步骤8),正如您所说:
  3.   

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

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

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

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

答案 2 :(得分:1)

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

具有OpenId连接身份服务的

Oauth2通常提供颁发者端点,您可以在其中找到jwk的URL(通过附加“ .well-known / openid-configuration”),该URL可用于验证JWT令牌及其内容是否由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属性名称,令牌验证方法等)(Openid connect试图统一此过程) )我建议您使用一些中间件身份提供程序,例如Oauth0(服务而非协议)或Keycloak。这些可以与外部身份提供程序(如您提到的社交页面)一起使用,还可以为您提供自定义用户存储。

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

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

答案 3 :(得分:0)

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

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

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