Facebook Canvas应用程序需要签名的请求吗?

时间:2011-12-04 12:28:20

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

我看到OAuth也推荐用于facebook画布应用程序,而且我的代码没有用,它尝试使用signed_request,但每次我更改会话时都要重新加载:

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))

我将上面的代码更改为画布应用程序的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']) 

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

由于

1 个答案:

答案 0 :(得分:2)

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

使用oauth获得的access_token具有相同的到期时间,因此如果您仅依靠oauth,则必须每小时对用户进行身份验证。

我倾向于使用两者的组合。在初次使用应用时,我使用oauth获取access_token并访问API。然后,在后续访问中,我依靠signed_request获取access_token并自动为用户个性化内容(因为它告诉我他们是谁并让我无需关注OAuth即可访问API再次流动)。