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


68

前言

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

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

要求

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

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

工作流程

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

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

  1. 客户端:使用JavaScript SDK启动登录Facebook
  2. Facebook:用户验证并批准应用权限(例如,访问用户的公开资料)
  3. Facebook:将响应发送到客户端,其中包含用户的访问令牌,ID和已签名的请求
  4. 客户端:将用户访问令牌存储在浏览器会话中(由SDK方便地处理
  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(将会),则该策略可能可行。但是,性能如何比较?

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

  3. 上面的步骤3中的响应上是否存在signedRequest字段(在此处提到),是否等同于“游戏画布登录”流程中此处的signed请求参数?

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

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

    解码和比较FB文档

    我想知道是否可以利用此方法来代替对debug_token图API的出站调用(上述步骤#6),以按照此处的建议确认访问令牌:

    FB文档中的debug_token图API

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

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

谢谢!



您在2014年问过您的问题。.五年后(现在是2019年),您实施了什么?好奇地知道,您的轮廓上方几乎是您最终要做什么的吗?
joedotnot

Answers:


26

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

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

步骤将是:

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


4
Tobi,感谢您抽出宝贵的时间回复。抱歉,我的评论很有限。阅读后我的想法:1.一些具体问题未得到解决。2.您建议的代码流如何用于验证对Web服务的客户端请求?您能说明一下客户端与服务器之间的交互吗?按照您现在的回答,其中没有关于客户的话题。也许从我最初的问题尚不清楚,但是我觉得您在工作流步骤中误解了“服务器”实体的角色。我已编辑问题以澄清问题。很抱歉给您带来任何混乱。
Scott Lin

access_token = {app-token-or-admin-token}我正在为此使用应用程序ID,它显示“消息”:“无效的OAuth访问令牌。”,用于检查访问令牌?
jayant singh

我悬赏这个问题,希望有人能填补您的答案中的空白。但是他们没有,您的仍然是最全面的,所以要加分!
邓肯·琼斯

谢谢@DuncanJones您还剩下什么问题?也许我可以帮忙...
Tobi

您是否建议将用户信息(例如,姓名,电子邮件,电话)存储在数据库中以便下次登录?
阿里

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

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

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

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

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

(抱歉,我无法回答第3和第4个问题)。


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令牌及其内容是否由同一身份服务签名。(即访问令牌源自为您提供jwk的同一服务)

例如,一些已知的openid连接身份提供程序:
https : //accounts.google.com/.well-known/openid-configuration
https://login.microsoftonline.com/common/v2.0/.well-known/openid-配置
(但Attlasian仅提供这两项服务来执行外部登录并不是巧合)

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.