在Google App Engine中管理用户身份验证

low*_*ing 5 python google-app-engine google-api google-cloud-datastore

我正在开发基于谷歌应用引擎的网络应用程序.该应用程序使用谷歌身份验证api.基本上每个处理程序都从这个BaseHandler扩展,并且作为任何get/post的第一个操作,checkAuth被执行.

class BaseHandler(webapp2.RequestHandler):
googleUser = None
userId = None
def checkAuth(self):
    user = users.get_current_user()
    self.googleUser = user;
    if user:
        self.userId = user.user_id()
        userKey=ndb.Key(PROJECTNAME, 'rootParent', 'Utente', self.userId)
        dbuser = MyUser.query(MyUser.key==userKey).get(keys_only=True)
        if dbuser:
            pass
        else:
            self.redirect('/')
    else:
        self.redirect('/')
Run Code Online (Sandbox Code Playgroud)

这个想法是它重定向到/如果没有用户通过谷歌登录或者如果我的用户数据库中没有用户具有该谷歌ID.

问题是我可以成功登录我的网络应用程序并进行操作.然后,从gmail,o从任何谷歌帐户注销,但如果我尝试继续使用它的工作的网络应用程序.这意味着users.get_current_user()仍然返回有效用户(有效但实际上是OLD).那可能吗?

重要更新 我了解Alex Martelli评论中解释的内容:有一个cookie可以保持前GAE认证有效.问题是,同一个网络应用程序还利用Google Api Client Library for Python https://developers.google.com/api-client-library/python/在Drive和Calendar上执行操作.在GAE应用程序中,可以通过实现整个OAuth2 Flow的装饰器轻松使用此类库(https://developers.google.com/api-client-library/python/guide/google_app_engine).

因此,我的处理程序get/post方法用oauth_required这样装饰

class SomeHandler(BaseHandler):
    @DECORATOR.oauth_required
    def get(self):
        super(SomeHandler,self).checkAuth()
        uid = self.googleUser.user_id()
        http = DECORATOR.http()
        service = build('calendar', 'v3')
        calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
Run Code Online (Sandbox Code Playgroud)

装饰者在哪里

   from oauth2client.appengine import OAuth2Decorator

   DECORATOR = OAuth2Decorator(
  client_id='XXXXXX.apps.googleusercontent.com',
  client_secret='YYYYYYY',
  scope='https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file'

        )
Run Code Online (Sandbox Code Playgroud)

它通常工作正常.然而(!!)当应用程序闲置很长时间时,oauth2装饰器会将我重定向到Google身份验证页面,如果我更改帐户(我有2个不同的帐户),则会发生一些问题:应用程序仍然记录为前帐户(通过users.get_current_user()检索),而api客户端库,以及oauth2装饰器,返回属于第二个帐户的数据(驱动器,日历等).

这真的不合适.

按照上面的示例(SomeHandler类)假设我被记录为帐户A. users.get_current_user()始终按预期返回A. 现在假设我停止使用该应用程序,很长一段时间后oauth_required将我重定向到Google帐户页面.因此,我决定(或犯错误)将日志记录为帐户B.当访问SomeHandler类的Get方法时,userId(通过users.get_current_user()重试是A,而通过服务对象返回的日历列表(Google Api) client Library)是属于B(实际当前登录的用户)的日历列表.

难道我做错了什么?是预期的东西?

另一个更新

这是在Martelli的回答之后.我更新了这样的处理程序:

class SomeHandler(BaseHandler):
    @DECORATOR.oauth_aware
    def get(self):
        if DECORATOR.has_credentials():
            super(SomeHandler,self).checkAuth()
            uid = self.googleUser.user_id()
            try:
               http = DECORATOR.http()
               service = build('calendar', 'v3')
               calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
            except (AccessTokenRefreshError, appengine.InvalidXsrfTokenError):
                self.redirect(users.create_logout_url(
                    DECORATOR.authorize_url()))
        else:
           self.redirect(users.create_logout_url(
              DECORATOR.authorize_url()))
Run Code Online (Sandbox Code Playgroud)

所以基本上我现在使用oauth_aware,如果没有凭据,我会注销用户并将其重定向到DECORATOR.authorize_url()

我注意到在一段时间不活动之后,处理程序引发了AccessTokenRefreshError和appengine.InvalidXsrfTokenError异常(但has_credentials()方法返回True).我抓住他们并(再次)将流重定向到logout和authorize_url()

它似乎工作,似乎对帐户切换很健壮.这是一个合理的解决方案还是我不考虑问题的某些方面?

Ale*_*lli 2

我理解这种混乱,但系统“按设计运行”。

在任何时间点,GAE 处理程序都可以有零个或一个“登录用户”(由 返回的对象users.get_current_user(),或者None如果没有登录用户)零个或多个“oauth2 授权令牌”(对于已登录的任何用户和范围)。已授予且未撤销)。

没有任何约束强制 oauth2 事物在任何意义上匹配“登录用户(如果有)”。

我建议您查看https://code.google.com/p/google-api-python-client/source/browse/samples/appengine/main.py上的非常简单的示例(要运行它,您将拥有克隆整个“google-api-python-client”包,然后复制到目录中google-api-python-client/source/browse/samples/appengineapiclient/oauth2client/同一个包以及https://github.com/jcgregorio/httplib2httplib2中复制- 并且还可以自定义- 但是,您不需要运行它,只需阅读并遵循代码即可)。client_secrets.json

该示例甚至不使用 users.get_current_user()- 它不需要它也不关心它:它只显示如何使用oauth2,并且持有授权令牌和服务之间没有任何联系。(例如,这允许您让 cron 代表一个或多个用户稍后执行某些任务 - cron 不会登录,但这并不重要 - 如果 oauth2 令牌正确存储和检索,那么它可以使用他们)。oauth2users

因此,代码从客户端机密创建一个装饰器,scope='https://www.googleapis.com/auth/plus.me'然后使用@decorator.oauth_required处理程序get来确保授权,并使用装饰器的授权http,它获取

user = service.people().get(userId='me').execute(http=http)
Run Code Online (Sandbox Code Playgroud)

service之前构建的一样discovery.build("plus", "v1", http=http)(具有不同的非授权http)。

如果您在本地运行此程序,则很容易添加虚假登录(请记住,用户登录是使用 dev_appserver 伪造的),以便users.get_current_user()返回princess@bride.com或您在虚假登录屏幕上输入的任何其他虚假电子邮件 - 并且这绝不会抑制完全独立的oauth2流程仍然按预期执行(即,与没有任何此类虚假登录的情况完全相同)。

oauth2如果您将修改后的应用程序(使用额外的用户登录名)部署到生产环境,则登录名必须是真实的 - 但它与应用程序的一部分无关,并且是独立的。

如果您的应用程序的逻辑确实需要将令牌限制oauth2为也登录到您的应用程序的特定用户,则您必须自己实现这一点 - 例如,通过设置scope'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read'(加上您需要的其他任何内容),您将从service.people().get(userId='me')对象user中获得(除其他外)一个emails属性,您可以在其中检查授权令牌是否适用于您打算授权的电子邮件地址的用户(并采取其他补救措施,例如通过注销 URL 等)。((这可以更简单地完成,无论如何我怀疑您是否真的需要这样的功能,但是,只是想提一下))。