当我的用户使用 gae-simpleauth 登录时,如何从 Google 帐户中检索电子邮件地址并将其存储在我的应用程序的用户配置文件中?

Posted

技术标签:

【中文标题】当我的用户使用 gae-simpleauth 登录时,如何从 Google 帐户中检索电子邮件地址并将其存储在我的应用程序的用户配置文件中?【英文标题】:How can I retrieve the email address from a Google Account and store it in my app's user profile when my user logs in using gae-simpleauth? 【发布时间】:2013-03-22 14:40:27 【问题描述】:

我正在尝试使用 webapp2 的 auth 库构建一个非常简单的用户权限系统。我正在使用gae-simpleauth 让用户使用他们的 Google 帐户登录。我希望将用户的电子邮件地址与允许的电子邮件地址列表进行比较,以确定用户是否有权访问资源,但我不清楚如何将电子邮件地址从 Google 帐户获取到我的帐户中应用程序。用户目前可以登录,但电子邮件地址似乎不是 simpleauth 默认添加到其帐户的内容。

如何使用 gae-simpleauth 从 Google 检索电子邮件地址并将其存储在我的应用的用户配置文件中?

我的 gae-simpleauth 实现几乎与示例相同,增加了 get_user_and_flags 函数,如果用户的电子邮件在 secrets.py 的列表中,则该函数会获取登录用户并设置管理员标志。不幸的是,这不起作用,因为用户没有电子邮件属性。

# -*- coding: utf-8 -*-
import logging, secrets, webapp2
from google.appengine.api import users
from webapp2_extras import auth, sessions, jinja2
from jinja2.runtime import TemplateNotFound
from lib.simpleauth import SimpleAuthHandler

def get_user_and_flags(self):
    """Returns the current user and permission flags for that user"""
    flags = 
    user = None
    if self.logged_in:
        user = self.current_user
        flags = 
           'admin': user.email in secrets.ADMIN_USERS,
            
    return user, flags

def simpleauth_login_required(handler_method):
    """A decorator to require that a user be logged in to access a handler.

    To use it, decorate your get() method like this:


        @simpleauth_login_required
        def get(self):
            user = self.current_user
            self.response.out.write('Hello, ' + user.name())
    """
    def check_login(self, *args, **kwargs):
        if self.request.method != 'GET':
            self.abort(400, detail='The login_required decorator '
                                   'can only be used for GET requests.')

        if self.logged_in:
            handler_method(self, *args, **kwargs)
        else:
            self.session['original_url'] = self.request.url.encode('ascii', 'ignore')
            self.redirect('/login/')

    return check_login

class BaseRequestHandler(webapp2.RequestHandler):
  def dispatch(self):
    # Get a session store for this request.
    self.session_store = sessions.get_store(request=self.request)

    try:
      # Dispatch the request.
      webapp2.RequestHandler.dispatch(self)
    finally:
      # Save all sessions.
      self.session_store.save_sessions(self.response)

  @webapp2.cached_property    
  def jinja2(self):
    """Returns a Jinja2 renderer cached in the app registry"""
    return jinja2.get_jinja2(app=self.app)

  @webapp2.cached_property
  def session(self):
    """Returns a session using the default cookie key"""
    return self.session_store.get_session()

  @webapp2.cached_property
  def auth(self):
      return auth.get_auth()

  @webapp2.cached_property
  def current_user(self):
    """Returns currently logged in user"""
    user_dict = self.auth.get_user_by_session()
    return self.auth.store.user_model.get_by_id(user_dict['user_id'])

  @webapp2.cached_property
  def logged_in(self):
    """Returns true if a user is currently logged in, false otherwise"""
    return self.auth.get_user_by_session() is not None


  def render(self, template_name, template_vars=):
    # Preset values for the template
    values = 
      'url_for': self.uri_for,
      'logged_in': self.logged_in,
      'flashes': self.session.get_flashes()
    

    # Add manually supplied template values
    values.update(template_vars)

    # read the template or 404.html
    try:
      self.response.write(self.jinja2.render_template(template_name, **values))
    except TemplateNotFound:
      self.abort(404)

  def head(self, *args):
    """Head is used by Twitter. If not there the tweet button shows 0"""
    pass


class ProfileHandler(BaseRequestHandler):
  def get(self):
    """Handles GET /profile"""    
    if self.logged_in:
      self.render('profile.html', 
        'user': self.current_user, 
        'session': self.auth.get_user_by_session()
      )
    else:
      self.redirect('/')


class AuthHandler(BaseRequestHandler, SimpleAuthHandler):
  """Authentication handler for OAuth 2.0, 1.0(a) and OpenID."""

  # Enable optional OAuth 2.0 CSRF guard
  OAUTH2_CSRF_STATE = True

  USER_ATTRS = 
    'facebook' : 
      'id'     : lambda id: ('avatar_url', 
        'http://graph.facebook.com/0/picture?type=large'.format(id)),
      'name'   : 'name',
      'link'   : 'link'
    ,
    'google'   : 
      'picture': 'avatar_url',
      'name'   : 'name',
      'link'   : 'link'
    ,
    'windows_live': 
      'avatar_url': 'avatar_url',
      'name'      : 'name',
      'link'      : 'link'
    ,
    'twitter'  : 
      'profile_image_url': 'avatar_url',
      'screen_name'      : 'name',
      'link'             : 'link'
    ,
    'linkedin' : 
      'picture-url'       : 'avatar_url',
      'first-name'        : 'name',
      'public-profile-url': 'link'
    ,
    'foursquare'   : 
      'photo'    : lambda photo: ('avatar_url', photo.get('prefix') + '100x100' + photo.get('suffix')),
      'firstName': 'firstName',
      'lastName' : 'lastName',
      'contact'  : lambda contact: ('email',contact.get('email')),
      'id'       : lambda id: ('link', 'http://foursquare.com/user/0'.format(id))
    ,
    'openid'   : 
      'id'      : lambda id: ('avatar_url', '/img/missing-avatar.png'),
      'nickname': 'name',
      'email'   : 'link'
    
  

  def _on_signin(self, data, auth_info, provider):
    """Callback whenever a new or existing user is logging in.
     data is a user info dictionary.
     auth_info contains access token or oauth token and secret.
    """
    auth_id = '%s:%s' % (provider, data['id'])
    logging.info('Looking for a user with id %s', auth_id)

    user = self.auth.store.user_model.get_by_auth_id(auth_id)
    _attrs = self._to_user_model_attrs(data, self.USER_ATTRS[provider])

    if user:
      logging.info('Found existing user to log in')
      # Existing users might've changed their profile data so we update our
      # local model anyway. This might result in quite inefficient usage
      # of the Datastore, but we do this anyway for demo purposes.
      #
      # In a real app you could compare _attrs with user's properties fetched
      # from the datastore and update local user in case something's changed.
      user.populate(**_attrs)
      user.put()
      self.auth.set_session(
        self.auth.store.user_to_dict(user))

    else:
      # check whether there's a user currently logged in
      # then, create a new user if nobody's signed in, 
      # otherwise add this auth_id to currently logged in user.

      if self.logged_in:
        logging.info('Updating currently logged in user')

        u = self.current_user
        u.populate(**_attrs)
        # The following will also do u.put(). Though, in a real app
        # you might want to check the result, which is
        # (boolean, info) tuple where boolean == True indicates success
        # See webapp2_extras.appengine.auth.models.User for details.
        u.add_auth_id(auth_id)

      else:
        logging.info('Creating a brand new user')
        ok, user = self.auth.store.user_model.create_user(auth_id, **_attrs)
        if ok:
          self.auth.set_session(self.auth.store.user_to_dict(user))

    # Remember auth data during redirect, just for this demo. You wouldn't
    # normally do this.
    self.session.add_flash(data, 'data - from _on_signin(...)')
    self.session.add_flash(auth_info, 'auth_info - from _on_signin(...)')

    # Go to the last page viewed
    target = str(self.session['original_url'])
    self.redirect(target)

  def logout(self):
    self.auth.unset_session()
    self.redirect('/')

  def handle_exception(self, exception, debug):
    logging.error(exception)
    self.render('error.html', 'exception': exception)

  def _callback_uri_for(self, provider):
    return self.uri_for('auth_callback', provider=provider, _full=True)

  def _get_consumer_info_for(self, provider):
    """Returns a tuple (key, secret) for auth init requests."""
    return secrets.AUTH_CONFIG[provider]

  def _to_user_model_attrs(self, data, attrs_map):
    """Get the needed information from the provider dataset."""
    user_attrs = 
    for k, v in attrs_map.iteritems():
      attr = (v, data.get(k)) if isinstance(v, str) else v(data.get(k))
      user_attrs.setdefault(*attr)

    return user_attrs

【问题讨论】:

【参考方案1】:

希望这个帮助(我有同样的问题)

secrets.py 中的第一个更改:

'google': (GOOGLE_APP_ID, GOOGLE_APP_SECRET, 'https://www.googleapis.com/auth/userinfo.profile'),

'google': (GOOGLE_APP_ID,GOOGLE_APP_SECRET, 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'),

auth.py 中更改 USER_ATTRS =


 ...
 'google'   : 
      'picture': 'avatar_url',
      'email'   : 'email', <-- new attr
      'name'   : 'name',
      'link'   : 'link'
    ,

【讨论】:

这看起来很有希望。谢谢您的发布。我现在正在尝试。 我已经按照你的建议实现了这个,但我仍然遇到同样的错误。只是为了确认一下,我应该将额外的 URL 添加到相同的字符串中,中间有一个空格,对吗? 当我的应用程序来到这行代码'admin': user.email in secrets.ADMIN_USERS我得到这个错误:AttributeError: 'User' object has no attribute 'email' 不确定你得到了什么,我在https://docs.google.com/file/d/0BwOnfUDw6AIoNlpWSVJDcEpzTTg/edit?usp=sharing 修改了 simpauth 的示例。它在我的本地主机中运行正常【参考方案2】:

由于您的问题不包含代码 sn-p,我只能猜测您到目前为止所做的事情。鉴于此,下面的代码应该可以工作:

from google.appengine.api import users

user = users.get_current_user()

email = user.email()

【讨论】:

这对我不起作用。 user.get_current_user() 正在返回 None,即使我已登录。似乎 simpleauth 可能不与 App Engine 用户 API 对话。 我将我的 simpleauth 实现添加到我的原始帖子中。这是你需要的吗?我不确定它是否有助于回答我的问题。我认为我不需要发布我的代码,因为我只需要知道如何让 simpleauth 将电子邮件地址添加到我的用户个人资料中。我想我可以使用这个的通用实现。我只是不知道该怎么做。【参考方案3】:

按照 nguyên 的想法,我还添加了自定义“_to_user_model_attrs”方法。 这是我的一段代码:

    def _to_user_model_attrs(self, data, attrs_map):
    """Get the needed information from the provider dataset."""

    user_attrs = 
    for k, v in attrs_map.iteritems():

        if v =="email":
            attr = (v, data.get(k)[0].get('value'))
        else:
            attr = (v, data.get(k)) if isinstance(v, str) else v(data.get(k))
        user_attrs.setdefault(*attr)

    return user_attrs

它对我有用!

【讨论】:

【参考方案4】:

似乎有几种身份验证方法,混合匹配不起作用。如果您还没有,请务必阅读此内容,https://developers.google.com/appengine/articles/auth

有几个部分可能是相对的,具体取决于您在 Google 上所做的其他事情。

Google Apps 帐户转换带来的变化

配置 Google Apps 以在 Appspot 上进行身份验证

【讨论】:

以上是关于当我的用户使用 gae-simpleauth 登录时,如何从 Google 帐户中检索电子邮件地址并将其存储在我的应用程序的用户配置文件中?的主要内容,如果未能解决你的问题,请参考以下文章

当我的用户尝试登录到他们的仪表板时,出现尝试获取非对象错误的属性

使用 Facebook 登录我的应用

使用 Laravel Guards 登录后,如何限制用户不访问登录页面?

颤动的firebase登录问题

配置 Spring Web 安全登录后给我无效的用户名和密码错误

当我登录到我的网络主页时,它无法正常工作