python multi 在Google App Engine中管理用戶身份驗證



google cloud datastore python example (1)

我正在基於谷歌應用程序引擎的Web應用程序。 該應用程序使用谷歌身份驗證apis。 基本上每個處理程序都從這個BaseHandler擴展而來,並作為執行checkAuth的任何get / post操作的第一個操作。

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

這個想法是,它重定向到/如果沒有用戶通過谷歌登錄或者如果沒有用戶在我的數據庫用戶有該谷歌ID。

問題是,我可以成功登錄我的網絡應用程序,並進行操作。 然後,從Gmail,Ø從任何谷歌帳戶註銷,但如果我嘗試繼續使用它的網絡應用程序的作品。 這意味著users.get_current_user()仍然返回一個有效的用戶(有效的,但實際上是舊的)。 那可能嗎?

重要更新我了解在Alex Martelli的評論中解釋了什麼:有一個cookie保持了以前的GAE認證有效。 問題在於,相同的Web應用程序還利用Google Api Client Library for Python https://developers.google.com/api-client-library/python/在雲端硬盤和日曆上執行操作。 在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)

裝飾者在哪裡

   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個不同的賬戶),WEIRD發生了什麼:應用程序仍然記錄為前一個帳戶(通過users.get_current_user()獲取),而api客戶端庫,以及oauth2裝飾器返回屬於第二個帳戶的數據(驅動器,日曆等)。

這真的不合適。

以上例子(SomeHandler類)假設我被記錄為帳戶A. users.get_current_user()始終按預期返回A. 現在假設我停止使用該應用程序,過了很久,oauth_required將我重定向到Google帳戶頁面。 因此,我決定(或犯一個錯誤)登錄為Account B.訪問SomeHandler類的Get方法時,通過users.get_current_user()檢索的userId為A,而通過服務對象返回的日曆列表(Google Api客戶端庫)是屬於B(當前實際記錄的用戶)的日曆列表。

難道我做錯了什麼? 有什麼期望?

另一個更新

這是馬爾泰利之後的答案。 我已經更新了這樣的處理程序:

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

所以基本上我現在使用oauth_aware,如果沒有憑據,則註銷用戶並將其重定向到DECORATOR.authorize_url()

我注意到,經過一段時間的不活動,處理程序引發AccessTokenRefreshError和appengine.InvalidXsrfTokenError異常(但has_credentials()方法返回True)。 我抓住他們,並(再次)重定向流註銷和authorize_url()

這似乎工作,似乎是強大的帳戶切換。 這是一個合理的解決方案還是我不考慮這個問題的某些方面?

https://src-bin.com


Answer #1

我理解這個困惑,但係統是“按設計工作”的。

在任何時候,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”包,然後從這個包和httplib2複製到google-api-python-client/source/browse/samples/appengine目錄apiclient/oauth2client/ https://github.com/jcgregorio/httplib2 - 也可以自定義client_secrets.json - 但是,你不需要運行它,只是為了閱讀和遵循代碼)。

這個示例甚至不使用 users.get_current_user() - 它不需要它,也不關心它:它只顯示如何使用oauth2 ,並且在持有oauth2授權令牌和users服務之間沒有連接。 (這允許你例如讓cron代表一個或多個用戶執行特定的任務 - cron不會登錄,但是沒關係 - 如果oauth2令牌被正確地存儲和檢索,那麼它可以使用他們)。

所以代碼從客戶端的秘密,使用scope='https://www.googleapis.com/auth/plus.me' ,然後使用@decorator.oauth_required處理程序的get來確保授權,並與裝飾的授權的http ,它提取

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

與之前構建的service discovery.build("plus", "v1", http=http) (帶有不同的非授權http )。

如果你在本地運行這個程序,很容易添加一個虛假登錄(記住,用戶登錄是偽裝的dev_appserver),以便users.get_current_user()返回[email protected]或其他任何假冒的電子郵件在虛假登錄屏幕輸入 -這絕不會阻止完全獨立的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') a user對象(還有很多其他的東西)的emails屬性,在這個屬性中你可以檢查授權令牌是否是用戶想要授權的電子郵件(並採取其他措施,例如通過註銷URL&c)。 ((這可以做得更簡單,無論如何,我懷疑你真的需要這樣的功能,但只是想提到它))。