如何在服务器端完全启用Facebook OAuth 2.0?

8 投票
2 回答
5162 浏览
提问于 2025-04-17 06:53

我基本上想保存一个用户的Facebook ID,这样我就可以通过Facebook接收更多的内容。理想情况下,我希望有一个解决方案,不使用JavaScript和Cookie,只在服务器端处理,但没有找到具体的例子,只有一些指导,所以我整理了一个可以讨论的例子。以下是我认为在将用户链接到我网站的OAuth对话框时有效的代码:

https://www.facebook.com/dialog/oauth?client_id=164355773607006&redirect_uri=http://www.kewlbusiness.com/oauth

然后我这样处理,以获取用户数据:

class OAuthHandler(webapp2.RequestHandler):
    def get(self):
      args = dict(
        code = self.request.get('code'),
        client_id = facebookconf.FACEBOOK_APP_ID,
        client_secret = facebookconf.FACEBOOK_APP_SECRET,
        redirect_uri = 'http://www.koolbusiness.com/oauth',
      )
      file = urllib.urlopen("https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(args))
      try:
        token_response = file.read()
      finally:
        file.close()
      access_token = cgi.parse_qs(token_response)["access_token"][-1]
      graph = facebook.GraphAPI(access_token)
      user = graph.get_object("me")   
      self.response.out.write(user["id"])
      self.response.out.write(user["name"])

通过这个方法,我可以在我的网站上实现“用Facebook登录”,而不需要很多麻烦的JavaScript和我们不想要的Cookie。我想为我的网站启用“用Facebook登录”。它应该在没有Cookie和JavaScript的情况下工作,但他们首先让你使用JavaScript和Cookie。所以我做了一个解决方案,似乎可以在没有Cookie和JavaScript的情况下,仅使用OAuth 2.0:你能说说我的“解决方案”吗?我想实现的实际用途是简单地记录哪个Facebook用户在我的网站上做了什么,并允许Facebook账户以一种标准化的方式登录。

我只是觉得它应该在没有JavaScript SDK和Cookie的情况下工作,看来确实如此。你能告诉我这个“解决方案”的优缺点吗?我觉得它比JavaScript + Cookie要好得多,那为什么他们要让我们使用JavaScript和Cookie,而最简单的例子似乎完全可以在服务器端处理呢?

谢谢

更新

看起来是对的,行为也正常,我还可以使用用户数据与数据存储交互,并用Python将我的Facebook名字显示在首页上,而不需要JavaScript和Cookie:

class FBUser(db.Model):
    id = db.StringProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)
    name = db.StringProperty(required=True)
    profile_url = db.StringProperty()
    access_token = db.StringProperty(required=True)
    name = db.StringProperty(required=True)
    picture = db.StringProperty()
    email = db.StringProperty()
    friends = db.StringListProperty()
    dirty = db.BooleanProperty()

class I18NPage(I18NHandler):

    def get(self):
    if self.request.get('code'):
          args = dict(
            code = self.request.get('code'),
            client_id = facebookconf.FACEBOOK_APP_ID,
            client_secret = facebookconf.FACEBOOK_APP_SECRET,
            redirect_uri = 'http://www.kewlbusiness.com/',
          )
      logging.debug("client_id"+str(args))
          file = urllib.urlopen("https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(args))
          try:
        logging.debug("reading file")
            token_response = file.read()
        logging.debug("read file"+str(token_response))
          finally:
            file.close()
          access_token = cgi.parse_qs(token_response)["access_token"][-1]
          graph = main.GraphAPI(access_token)
          user = graph.get_object("me")   #write the access_token to the datastore
      fbuser = main.FBUser.get_by_key_name(user["id"])
          logging.debug("fbuser "+str(fbuser))

          if not fbuser:
            fbuser = main.FBUser(key_name=str(user["id"]),
                                id=str(user["id"]),
                                name=user["name"],
                                profile_url=user["link"],
                                access_token=access_token)
            fbuser.put()
          elif fbuser.access_token != access_token:
            fbuser.access_token = access_token
            fbuser.put()
           self.render_jinja(
                'home_jinja',request=self.request,fbuser=user,...

现在我有了一个Facebook用户的变量fbuser和一个Google用户的变量user,这让我可以在我的网站上使用Facebook,而不需要那些麻烦的JavaScript和Cookie。

现在我可以从我的网站上显示我的Facebook名字,这太好了,终于可以按照预期的方式工作,不依赖于JavaScript,也不需要Cookie。

为什么文档推荐使用JavaScript + Cookie,而服务器端的OAuth 2.0是最干净的解决方案?你同意这是最佳解决方案吗,因为它不依赖于使用JavaScript或Cookie?

更新

可能是重复的问题,因为我看到其他人无法通过服务器代码登出,他们不得不使用JavaScript SDK,而一个要求是它必须且应该在没有JavaScript的情况下工作,所以我做了一些调试,发现“清除”Cookie,我不得不更改Cookie的名称,虽然它有效,但我希望你能评论一下,或者测试一下你认为我的项目是如何解决这个问题的。像这样的登出链接应该有效,但实际上并不工作。如果我登出两次就可以,这就是一个奇怪的bug,因为我可以修复它,但我仍然不知道为什么登出需要第二次点击:

https://www.facebook.com/logout.php?next=http://www.myappengineproject.com&access_token=AAACVewZBArF4BACUDwnDap5OrQQ5dx0jsHEKPJkIJJ8GdXlYdni5K50xKw6s8BSIDZCpKBtVWF9maHMoJeF9ZCRRYM1zgZD

看起来我并不是唯一一个试图完全避免使用JavaScript来解决服务器端OAuth 2.0的人。人们可以做很多事情,但他们无法登出:

Facebook Oauth Logout

Facebook的OAuth 2.0官方文档说:

你可以通过将用户引导到以下URL来登出他们的Facebook会话:

https://www.facebook.com/logout.php?next=YOUR_URL&access_token=ACCESS_TOKEN

YOUR_URL必须是你网站域名中的一个URL,如开发者应用中定义的那样。

我想在服务器端做所有事情,我发现建议的链接方式留下了Cookie,这样登出链接就无法工作:

https://www.facebook.com/logout.php?next=http://{{host}}&access_token={{current_user.access_token}}

它确实重定向,但并没有将用户登出我的网站。看起来像是一个Heisenbug,因为这对我来说是变化的,而且没有太多文档。我似乎能够通过一个处理程序来实现功能,操作Cookie,从而有效地将用户登出我的网站:

class LogoutHandler(webapp2.RequestHandler):
    def get(self):
        self.set_cookie("fbsr_" + facebookconf.FACEBOOK_APP_ID, None, expires=time.time() - 86400)
        self.redirect("/")
    def set_cookie(self, name, value, expires=None):

        if value is None:
            value = 'deleted'
            expires = datetime.timedelta(minutes=-50000)
        jar = Cookie.SimpleCookie()
        jar[name] = value
        jar[name]['path'] = '/'
        if expires:
            if isinstance(expires, datetime.timedelta):
                expires = datetime.datetime.now() + expires
            if isinstance(expires, datetime.datetime):
                expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
            jar[name]['expires'] = expires
        self.response.headers.add_header(*jar.output().split(': ', 1))

所以将处理程序映射到/auth/logout并将其设置为链接,实际上可以将用户登出我的网站(希望不会将用户登出Facebook,尚未测试)。

我代码的其他部分处理OAuth令牌和Cookie查找,以便进行OAuth通信:

def get(self):
    fbuser=None
    profile = None
    access_token = None
    accessed_token = None
    logout = False
    if self.request.get('code'):
      args = dict(
        code = self.request.get('code'),
        client_id = facebookconf.FACEBOOK_APP_ID,
        client_secret = facebookconf.FACEBOOK_APP_SECRET,
        redirect_uri = 'http://self.get_host()/',
      )
      file = urllib.urlopen("https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(args))
      try:
        token_response = file.read()
      finally:
        file.close()
      access_token = cgi.parse_qs(token_response)["access_token"][-1]
      graph = main.GraphAPI(access_token)
      user = graph.get_object("me")   #write the access_token to the datastore
      fbuser = main.FBUser.get_by_key_name(user["id"])
      logging.debug("fbuser "+fbuser.name)

      if not fbuser:
        fbuser = main.FBUser(key_name=str(user["id"]),
                            id=str(user["id"]),
                            name=user["name"],
                            profile_url=user["link"],
                            access_token=access_token)
        fbuser.put()
      elif fbuser.access_token != access_token:
        fbuser.access_token = access_token
        fbuser.put()

    current_user = main.get_user_from_cookie(self.request.cookies, facebookconf.FACEBOOK_APP_ID, facebookconf.FACEBOOK_APP_SECRET)
    if current_user:
      graph = main.GraphAPI(current_user["access_token"])
      profile = graph.get_object("me")
      accessed_token = current_user["access_token"]

我没有单独制作登录处理程序,因为登录基本上就是我在根请求处理程序中的代码。我的用户类如下:

class FBUser(db.Model):
    id = db.StringProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)
    name = db.StringProperty(required=True)
    profile_url = db.StringProperty()
    access_token = db.StringProperty(required=True)
    name = db.StringProperty(required=True)
    picture = db.StringProperty()
    email = db.StringProperty()

我简单地组合了两个基本提供者。

enter image description here

我使用变量current_user表示Facebook用户,变量user表示Google用户,变量fbuser表示正在登录的用户,因此没有Cookie匹配。

我使用的Cookie查找代码如下,我认为我理解它,并且它能达到我想要的效果:

def get_user_from_cookie(cookies, app_id, app_secret):
    """Parses the cookie set by the official Facebook JavaScript SDK.

    cookies should be a dictionary-like object mapping cookie names to
    cookie values.

    If the user is logged in via Facebook, we return a dictionary with the
    keys "uid" and "access_token". The former is the user's Facebook ID,
    and the latter can be used to make authenticated requests to the Graph API.
    If the user is not logged in, we return None.

    Download the official Facebook JavaScript SDK at
    http://github.com/facebook/connect-js/. Read more about Facebook
    authentication at http://developers.facebook.com/docs/authentication/.
    """
    logging.debug('getting user by cookie')
    cookie = cookies.get("fbsr_" + app_id, "")
    if not cookie:
        logging.debug('no cookie found')
        return None
    logging.debug('cookie found')
    response = parse_signed_request(cookie, app_secret)
    if not response:
        logging.debug('returning none')
        return None

    args = dict(
        code = response['code'],
        client_id = app_id,
        client_secret = app_secret,
        redirect_uri = '',
    )

    file = urllib.urlopen("https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(args))
    try:
        token_response = file.read()
    finally:
        file.close()

    access_token = cgi.parse_qs(token_response)["access_token"][-1]
    logging.debug('returning cookie')
    return dict(
        uid = response["user_id"],
        access_token = access_token,
    )

我不得不学习Cookie来解决这个问题,希望你能多评论一下。输出欢迎消息需要3个变量,一个用于Google用户,一个用于已登录的Facebook用户,还有一个变量fbuser用于上述情况:

当你试图验证一个新用户时,会使用JavaScript/Cookie。这个用户在你的数据库中不存在,你也没有他的accessToken。

所以我不得不使用3个变量,也许你可以只用2个变量做到这一点?

    {% if user or current_user or fbuser %}
        <div id="user-ident">
            <span>{% trans %}Welcome,{% endtrans %} <b>{{ current_user.name }}{% if not current_user and user %}{{ user.nickname() }}{% endif %}{% if not current_user and not user and fbuser %}{{ fbuser.name }}{% endif %}</span>
        </div>
        {% endif %}

2 个回答

1

找到了这个。看起来不错,而且它不仅仅支持Facebook。你可以在这里查看:https://code.google.com/p/gae-simpleauth/

1

JavaScript和Cookies通常用来验证新用户的身份。当这个用户还没有在你的数据库里存在,并且你也没有他的访问令牌(accessToken)时,就会用到这些技术。

一旦你有了用户的访问令牌,你就可以通过任何编程语言访问Facebook的API,甚至可以不使用Cookies或JavaScript,只通过HTTP来实现。例如,有一个Python的客户端可以使用,链接在这里:http://github.com/pythonforfacebook/facebook-sdk(之前的链接是https://github.com/facebook/python-sdk

撰写回答