在Google App Engine中管理用户身份验证 [英] Managing users authentication in Google App Engine

查看:128
本文介绍了在Google App Engine中管理用户身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究基于谷歌应用引擎的webapp。
该应用程序使用Google身份验证API。
基本上每个处理程序都从这个BaseHandler扩展而来,并且作为执行checkAuth的任何get / post操作的第一个操作。

  class BaseHandler(webapp2.RequestHandler):
googleUser = None
userId = None
def checkAuth(self):
user = users.get_current_user()
self.googleUser = 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('/')

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



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



重要更新
我了解Alex Martelli的评论中解释了什么:保持原GAE认证有效的cookie。
问题在于,相同的Web应用程序也利用Google API的Google Api客户端库 https://developers.google.com/api-client-library/python/ 在云端硬盘和日历上执行操作。在GAE应用程序中,可以通过实现整个OAuth2 Flow的装饰器轻松使用此类库( https://developers.google.com/api-client-library/python/guide/google_app_engine )。



因此,我的Handlers 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)。执行(http = http)

装饰器在哪里

  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'


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



哪个真的不合适。

以上例子(SomeHandler类)假设我记录为Account A. users.get_current_user()始终按预期返回A.现在假设我停止使用该应用程序,过了很久,oauth_required将我重定向到Google帐户页面。因此,我决定(或犯一个错误)记录为Account B.当访问SomeHandler类的Get方法时,userId(通过users.get_current_user()进行检索为A),而通过服务对象返回的日历列表(Google Api客户端库)是属于B(当前实际记录的用户)的日历列表。



我做错了什么?是预期的吗?



另一个更新



这是Martelli的答案之后
我更新了处理程序this:

  class SomeHandler(BaseHandler):
@ DECORATOR.oauth_aware
def get(self):
如果DECORATOR.has_credentials():
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
try:
http = DECORATOR.http()
service = build('calendar','v3')
calendar_list =服务。除了(AccessTokenRefreshError,appengine.InvalidXsrfTokenError):
self.redirect(users.create_logout_url(
DECORATOR.authorize_url())之外, )))
else:
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))

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

我注意到经过一段时间不活动后,处理程序会引发AccessTokenRefreshError和appengine.InvalidXsrfTokenError异常(但has_credentials()方法返回True)。我赶上他们,并(再次)重定向流注销和authorize_url()

它似乎工作,似乎是强大的帐户切换。
这是一个合理的解决方案还是我没有考虑问题的某些方面?

解决方案

我理解混乱,但是系统按设计工作。



在任何时间点,GAE处理程序可以有零个或一个登录用户(由 users.get_current_user() None 如果没有登录用户)为零或更多的oauth2授权令牌(无论用户和范围已被授予且未被撤销)。

没有任何约束可以迫使oauth2的东西匹配,无论如何,已登录的用户,如果有的话。



我建议您查看 https://code.google.com/p/google-api-python-client/ source / browse / samples / appengine / main.py (要运行它,你必须克隆整个google-a pi-python-client包,然后复制到 google-api-python-client / source / browse / samples / appengine 目录目录 apiclient / 和 oauth2client / 来自同一个包,以及 httplib2 来自 https://github.com/jcgregorio/httplib2 - 还自定义 client_secrets.json - 但是,您不需要运行它,只需阅读并遵循代码即可)。



该示例甚至不会使用 users.get_current_user() - 它不需要它也不关心它:它只显示如何使用 oauth2 ,并且在持有 oauth2 - 授权令牌和用户之间存在 no 连接服务。 (这允许你例如让cron代表一个或多个用户执行特定的任务 - cron不会登录,但它并不重要 - 如果oauth2令牌被正确存储和检索,那么它可以使用它们)。

所以代码使用客户端的秘密制作一个装饰器,使用 scope ='https://www.googleapis.com/auth /plus.me',然后在处理程序的 get 中使用 @ decorator.oauth_required 来确保授权,并且使用装饰者授权的 http ,它获取

 用户= service.people()。get(userId ='me')。execute(http = http)

with service 以前构建为 discovery.build(plus,v1,http = http)(with一个不同的非授权 http )。



如果你在本地运行,很容易添加虚假登录请记住,用户登录伪装与dev_appserver),以便 users.get_current_user()返回 princess@bride.com 或其他任何您在假登录屏幕上输入的虚假电子邮件 - 这绝不会阻止完全分开的 oauth2 从仍然按照预期执行的流程(即,与没有任何此类虚假登录的方式完全相同)流动。



如果部署修改后的应用程序(带有额外的用户登录)到生产环境,登录必须是真实的 - 但它与 oauth2 部分一样无关紧要如果您的应用程序的逻辑确实需要限制 oauth2 标记给已登录到的特定用户你的应用程序,你必须自己实现 - 例如通过设置范围'https://www.googleapis.com/auth/ plus.login https://www.googleapis.com/auth/plus.profile.emails.read'(加上你需要的任何东西),你可以从 service .people()。get(userId ='me') a user objec (其中包含许多其他内容)电子邮件属性,您可以在该属性中检查授权令牌是否包含您打算授权的电子邮件的用户(并采取其他方式采取补救措施,例如通过注销URL& c)。 ((这可以做得更简单,无论如何,我怀疑你真的需要这样的功能,但只是想提及它))。


I am working on a webapp based on google app engine. The application uses the google authentication apis. Basically every handler extends from this BaseHandler and as first operation of any get/post the checkAuth is executed.

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('/')

The idea is that it redirects to / if no user is logged in via Google OR if there is not a User in my db of users having that google id.

The problem is that I can succesfully log in my web app and make operations. Then, from gmail, o Logout from any google account BUT if i try to keep using the web app it works. This means the users.get_current_user() still returns a valid user (valid but actually OLD). Is that possible?

IMPORTANT UPDATE I Do Understand what explained in the Alex Martelli's Comment: There is a cookie which keeps the former GAE authentication valid. The problem is that the same web app also exploits the Google Api Client Library for Python https://developers.google.com/api-client-library/python/ to perform operations on Drive and Calendar. In GAE apps such library can be easily used through decorators implementing the whole OAuth2 Flow (https://developers.google.com/api-client-library/python/guide/google_app_engine).

I therefore have my Handlers get/post methods decorated with oauth_required like this

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)

Where decorator is

   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'

        )

It usually works fine. However (!!) when the app is idle for a long time it happens that the oauth2 decorator redirects me to the Google authentication page where, if I change account (I have 2 different accounts) Something WEIRD happens: The app is still logged as the former account (retrieved through users.get_current_user()) while the api client library, and thus the oauth2 decorator, returns data (drive, calendar, etc.) belonging to the second account.

Which is REALLY not appropriate.

Following the example above (SomeHandler class) suppose I am logged as Account A. The users.get_current_user() always returns A as expected. Now suppose I stopped using the app, after a long while the oauth_required redirects me to the Google Account page. I therefore decide (or make a mistake) to log is as Account B. When accessing the Get method of the SomeHandler class the userId (retrived through users.get_current_user() is A while the list of calendars returned through the service object (Google Api client Library) is the list of calendars belonging to B (the actual currently logged user).

Am I doing something wrong? is Something expected?

Another Update

this is after the Martelli's Answer. I have updated the handlers like this:

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

so basically I now use oauth_aware and, in case of none credentials I logout the user and redirect it to the DECORATOR.authorize_url()

I have noticed that after a period of inactivity, the handler raises AccessTokenRefreshError and appengine.InvalidXsrfTokenError exceptions (but the has_credentials() method returns True). I catch them and (again) redirect the flow to the logout and authorize_url()

It seems to work and seems to be robust to accounts switch. Is it a reasonable solution or am I not considering some aspects of the issue?

解决方案

I understand the confusion, but the system is "working as designed".

At any point in time a GAE handler can have zero or one "logged-in user" (the object returned by users.get_current_user(), or None if no logged-in user) and zero or more "oauth2 authorization tokens" (for whatever users and scopes have been granted and not revoked).

There is no constraint that forces the oauth2 thingies to match, in any sense, the "logged-in user, if any".

I would recommend checking out the very simple sample at https://code.google.com/p/google-api-python-client/source/browse/samples/appengine/main.py (to run it, you'll have to clone the whole "google-api-python-client" package, then copy into the google-api-python-client/source/browse/samples/appengine directory directories apiclient/ and oauth2client/ from this same package as well as httplib2 from https://github.com/jcgregorio/httplib2 -- and also customize the client_secrets.json -- however, you don't need to run it, just to read and follow the code).

This sample doesn't even use users.get_current_user() -- it doesn't need it nor care about it: it only shows how to use oauth2, and there is no connection between holding an oauth2-authorized token, and the users service. (This allows you for example to have cron execute on behalf of one or more users certain tasks later -- cron doesn't log in, but it doesn't matter -- if the oauth2 tokens are properly stored and retrieved then it can use them).

So the code makes a decorator from the client secrets, with scope='https://www.googleapis.com/auth/plus.me', then uses @decorator.oauth_required on a handler's get to ensure authorization, and with the decorator's authorized http, it fetches

user = service.people().get(userId='me').execute(http=http)

with service built earlier as discovery.build("plus", "v1", http=http) (with a different non-authorized http).

Should you run this locally, it's easy to add a fake login (remember, user login is faked with dev_appserver) so that users.get_current_user() returns princess@bride.com or whatever other fake email you input at the fake login screen -- and this in no way inhibits the completely separate oauth2 flow from still performing as intended (i.e, exactly the same way as it does without any such fake login).

If you deploy the modified app (with an extra user login) to production, the login will have to be a real one -- but it's just as indifferent to, and separate from, the oauth2 part of the app.

If your application's logic does require constraining the oauth2 token to the specific user who's also logged into your app, you'll have to implement this yourself -- e.g by setting scope to 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read' (plus whatever else you need), you'll get from service.people().get(userId='me') a user object with (among many other things) an emails attribute in which you can check that the authorization token is for the user with the email you intended to authorize (and take remedial action otherwise, e.g via a logout URL &c). ((This can be done more simply and in any case I doubt you really need such functionality, but, just wanted to mention it)).

这篇关于在Google App Engine中管理用户身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆