python - Facebook Canvas 应用是否需要签名请求?

标签 python google-app-engine facebook-graph-api oauth

我看到 OAuth 也被推荐用于 facebook canvas 应用程序并且我没有工作的代码,它尝试使用 signed_request 但每次我更改 session 我都必须重新加载:

class Facebook(object):
    """Wraps the Facebook specific logic"""
    def __init__(self, app_id=conf.FACEBOOK_APP_ID,
            app_secret=conf.FACEBOOK_APP_SECRET):
        self.app_id = app_id
        self.app_secret = app_secret
        self.user_id = None
        self.access_token = None
        self.signed_request = {}

    def api(self, path, params=None, method=u'GET', domain=u'graph'):
        """Make API calls"""
        if not params:
            params = {}
        params[u'method'] = method
        if u'access_token' not in params and self.access_token:
            params[u'access_token'] = self.access_token
        result = json.loads(urlfetch.fetch(
            url=u'https://' + domain + u'.facebook.com' + path,
            payload=urllib.urlencode(params),
            method=urlfetch.POST,
            headers={
                u'Content-Type': u'application/x-www-form-urlencoded'})
            .content)
        if isinstance(result, dict) and u'error' in result:
            raise FacebookApiError(result)
        return result

    def load_signed_request(self, signed_request):
        """Load the user state from a signed_request value"""
        try:
            sig, payload = signed_request.split(u'.', 1)
            sig = self.base64_url_decode(sig)
            data = json.loads(self.base64_url_decode(payload))

            expected_sig = hmac.new(
                self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()

            # allow the signed_request to function for upto 1 day
            if sig == expected_sig and \
                    data[u'issued_at'] > (time.time() - 86400):
                self.signed_request = data
                self.user_id = data.get(u'user_id')
                self.access_token = data.get(u'oauth_token')
        except ValueError, ex:
            pass # ignore if can't split on dot

    @property
    def user_cookie(self):
        """Generate a signed_request value based on current state"""
        if not self.user_id:
            return
        payload = self.base64_url_encode(json.dumps({
            u'user_id': self.user_id,
            u'issued_at': str(int(time.time())),
        }))
        sig = self.base64_url_encode(hmac.new(
            self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
        return sig + '.' + payload

    @staticmethod
    def base64_url_decode(data):
        data = data.encode(u'ascii')
        data += '=' * (4 - (len(data) % 4))
        return base64.urlsafe_b64decode(data)

    @staticmethod
    def base64_url_encode(data):
        return base64.urlsafe_b64encode(data).rstrip('=')


class CsrfException(Exception):
    pass


class BaseHandler(webapp.RequestHandler):
    facebook = None
    user = None
    csrf_protect = True

    def initialize(self, request, response):
        """General initialization for every request"""
        super(BaseHandler, self).initialize(request, response)

        try:
            self.init_facebook()
            self.init_csrf()
            self.response.headers[u'P3P'] = u'CP=HONK' # iframe cookies in IE
        except Exception, ex:
            self.log_exception(ex)
            raise

    def handle_exception(self, ex, debug_mode):
        """Invoked for unhandled exceptions by webapp"""
        self.log_exception(ex)
        self.render(u'error',
            trace=traceback.format_exc(), debug_mode=debug_mode)

    def log_exception(self, ex):
        """Internal logging handler to reduce some App Engine noise in errors"""
        msg = ((str(ex) or ex.__class__.__name__) +
                u': \n' + traceback.format_exc())
        if isinstance(ex, urlfetch.DownloadError) or \
           isinstance(ex, DeadlineExceededError) or \
           isinstance(ex, CsrfException) or \
           isinstance(ex, taskqueue.TransientError):
            logging.warn(msg)
        else:
            logging.error(msg)

    def set_cookie(self, name, value, expires=None):
        """Set a cookie"""
        if value is None:
            value = 'deleted'
            expires = datetime.timedelta(minutes=-50000)
        jar = Cookie.SimpleCookie()
        jar[name] = value
        jar[name]['path'] = u'/'
        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(u': ', 1))

    def render(self, name, **data):
        """Render a template"""
        if not data:
            data = {}
        data[u'js_conf'] = json.dumps({
            u'appId': conf.FACEBOOK_APP_ID,
            u'canvasName': conf.FACEBOOK_CANVAS_NAME,
            u'userIdOnServer': self.user.user_id if self.user else None,
        })
        data[u'logged_in_user'] = self.user
        data[u'message'] = self.get_message()
        data[u'csrf_token'] = self.csrf_token
        data[u'canvas_name'] = conf.FACEBOOK_CANVAS_NAME
        self.response.out.write(template.render(
            os.path.join(
                os.path.dirname(__file__), 'templates', name + '.html'),
            data))

    def init_facebook(self):
        """Sets up the request specific Facebook and User instance"""
        facebook = Facebook()
        user = None

        # initial facebook request comes in as a POST with a signed_request
        if u'signed_request' in self.request.POST:
            facebook.load_signed_request(self.request.get('signed_request'))
            # we reset the method to GET because a request from facebook with a
            # signed_request uses POST for security reasons, despite it
            # actually being a GET. in webapp causes loss of request.POST data.
            self.request.method = u'GET'
            self.set_cookie(
                'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
        elif 'u' in self.request.cookies:
            facebook.load_signed_request(self.request.cookies.get('u'))

        # try to load or create a user object
        if facebook.user_id:
            user = User.get_by_key_name(facebook.user_id)
            if user:
                # update stored access_token
                if facebook.access_token and \
                        facebook.access_token != user.access_token:
                    user.access_token = facebook.access_token
                    user.put()
                # refresh data if we failed in doing so after a realtime ping
                if user.dirty:
                    user.refresh_data()
                # restore stored access_token if necessary
                if not facebook.access_token:
                    facebook.access_token = user.access_token

            if not user and facebook.access_token:
                me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
                try:
                    friends = [user[u'id'] for user in me[u'friends'][u'data']]
                    user = User(key_name=facebook.user_id,
                        user_id=facebook.user_id, friends=friends,
                        access_token=facebook.access_token, name=me[u'name'],
                        email=me.get(u'email'), picture=me[u'picture'])
                    user.put()
                except KeyError, ex:
                    pass # ignore if can't get the minimum fields

        self.facebook = facebook
        self.user = user

    def init_csrf(self):
        """Issue and handle CSRF token as necessary"""
        self.csrf_token = self.request.cookies.get(u'c')
        if not self.csrf_token:
            self.csrf_token = str(uuid4())[:8]
            self.set_cookie('c', self.csrf_token)
        if self.request.method == u'POST' and self.csrf_protect and \
                self.csrf_token != self.request.POST.get(u'_csrf_token'):
            raise CsrfException(u'Missing or invalid CSRF token.')

    def set_message(self, **obj):
        """Simple message support"""
        self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)

    def get_message(self):
        """Get and clear the current message"""
        message = self.request.cookies.get(u'm')
        if message:
            self.set_message() # clear the current cookie
            return json.loads(base64.b64decode(message))

我将上面的代码更改为 Canvas 应用程序的 OAuth 服务器端,然后我可以让应用程序按照我想要的方式运行。但是如果我使用 OAuth 2.0,我真的需要 signed_request 吗?如果我使用 OAuth,则 signed_request 似乎是不必要的,而 OAuth 可以做到这一切。我对函数 init_facebook 做了相当多的更改:

def init_facebook(self):

    facebook = Facebook()
    user = None

    # initial facebook request comes in as a POST with a signed_request
    if 'signed_request' in self.request.POST:
        fbdata= parse_signed_request(self.request.get('signed_request'), facebookconf.FACEBOOK_APP_SECRET)

        facebook.signed_request = fbdata
        facebook.user_id = fbdata.get('user_id')
        facebook.access_token = fbdata.get('oauth_token')

        if facebook.user_id:
          graph = GraphAPI(facebook.access_token)
          user = graph.get_object("me")   #write the access_token to the datastore
          fbuser = FBUser.get_by_key_name(user["id"])
          #logging.debug("fbuser "+fbuser.name)
          self.user = fbuser
          if not fbuser:
            fbuser = FBUser(key_name=str(user["id"]),
                            id=str(user["id"]),
                            name=user["name"],
                            profile_url=user["link"],
                            access_token=facebook.access_token)
            fbuser.put()
          elif fbuser.access_token != facebook.access_token:
            fbuser.access_token = facebook.access_token
            fbuser.put()

    # try to load or create a user object
    if facebook.user_id:
        logging.debug("loading facebook.user_id")
        user = FBUser.get_by_key_name(facebook.user_id)
        if user:
            # update stored access_token
            if facebook.access_token and \
                    facebook.access_token != user.access_token:
                user.access_token = facebook.access_token
                user.put()
            # refresh data if we failed in doing so after a realtime ping
            if user.dirty:
                user.refresh_data()
            # restore stored access_token if necessary
            if not facebook.access_token:
                facebook.access_token = user.access_token

        if not user and facebook.access_token:
            me = facebook.api('/me', {'fields': _USER_FIELDS})
            try:
                friends = [user['id'] for user in me['friends']['data']]
                user = FBUser(key_name=facebook.user_id,
                    id=facebook.user_id, friends=friends,
                    access_token=facebook.access_token, name=me['name'],
                    email=me.get('email'), picture=me['picture'])
                user.put()
            except KeyError, ex:
                pass # ignore if can't get the minimum fields

    self.facebook = facebook
    self.user = user

现在它使用 OAuth,而不是将方法从 POST 更改为 GET,我一直不明白为什么它必须这样做。我有更多代码,但也许您已经知道得足够多了,可以告诉我我是否做错了并且应该回到更基本的示例。

例如,我在注销用户时遇到了一些问题,我不得不编写一个自定义注销处理程序:

class FBLogoutHandler(webapp2.RequestHandler):
    def get(self):
    logging.debug('in fblogout')
        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"] 
    logging.debug('setting cookie')
        self.set_cookie("fbsr_" + facebookconf.FACEBOOK_APP_ID, None, expires=time.time() - 86400)
    logging.debug('redirecting with token '+str(accessed_token))
        self.redirect('https://www.facebook.com/logout.php?next=http://www.facebook.com&access_token=%s' % accessed_token)
    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))
    def get_host(self):
        return os.environ.get('HTTP_HOST', os.environ['SERVER_NAME']) 

这个解决方案可能不是最好的,所以我想知道我的 Canvas 应用程序有哪些替代方案?

谢谢

最佳答案

signed_request 始终在您访问 Canvas 应用程序时更新(刷新父框架时),因此这是为用户获取最新的 access_token 的好方法(通常只持续一个小时)。

使用oauth获得的access_token有相同的过期时间,所以如果你完全依赖oauth,你将不得不每小时验证一次用户。

我倾向于结合使用两者。在初次使用该应用程序时,我使用 oauth 获取 access_token 并访问 API。然后,在随后的访问中,我依靠 signed_request 获取 access_token 并自动为用户个性化内容(因为它告诉我他们是谁并允许我访问API 而无需再次遵循 OAuth 流程)。

关于python - Facebook Canvas 应用是否需要签名请求?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8375283/

相关文章:

python - 尝试排除 "OSError: [Errno 13] Permission denied"错误并写入日志文件

python - Python 数学中 prod() 的时间复杂度是多少

python - 如何在 Windows 上安装 pipx?

python - App Engine 上的 Pyramid 获取 "InvalidResponseError: header values must be str, got ' unicode'

php - 如何将 Facebook 页面帖子作为博客帖子拉入 WordPress?

facebook - 使用 FQL 获取所有 facebook 消息

python - CV2 : Unwanted cropping after dewarping (initUndistortRectifyMap)

google-app-engine - 在 golang 中使用 datastore.GetAll 没有得到结果

python - 存储电子邮件的标准

ios - Facebook iOS SDK - 设置 Controller 的正确方法是什么