首页 文章

将Google OAuth2与Flask一起使用

提问于
浏览
53

任何人都可以指出一个完整的示例,使用OAuth2和Flask对Google帐户进行身份验证,而不是在App Engine上进行身份验证吗?

我正在尝试让用户访问Google日历,然后使用该访问权限从日历中检索信息并进一步处理 . 我还需要存储并稍后刷新OAuth2令牌 .

我查看了Google的oauth2client库并且可以开始跳舞以检索授权代码,但是我知道我需要请求刷新令牌和访问令牌,但库中提供的示例适用于App仅限引擎和Django .

我也尝试使用包含对OAuth2的引用的Flask's OAuth module,但我也没有办法在那里交换授权代码 .

我可以手动编写请求代码,但更愿意使用或改编现有的python模块,使请求变得简单,正确处理可能的响应,甚至可以帮助存储令牌 .

有这样的事吗?

8 回答

  • 1

    另一个答案提到Flask-Rauth,但没有详细说明如何使用它 . 有一些谷歌特定的陷阱,但我最终实现了它,它运作良好 . 我将它与Flask-Login集成,因此我可以使用像 @login_required 这样的有用糖来装饰我的观点 .

    我希望能够支持多个OAuth2提供商,因此部分代码是通用的,并且基于Miguel Grinberg关于通过Facebook和Twitter支持OAuth2的优秀帖子here .

    首先,将您的Google特定Google身份验证信息添加到您应用的配置中:

    GOOGLE_LOGIN_CLIENT_ID = "<your-id-ending-with>.apps.googleusercontent.com"
    GOOGLE_LOGIN_CLIENT_SECRET = "<your-secret>"
    
    OAUTH_CREDENTIALS={
            'google': {
                'id': GOOGLE_LOGIN_CLIENT_ID,
                'secret': GOOGLE_LOGIN_CLIENT_SECRET
            }
    }
    

    当你创建你的应用程序时(在我的情况下,模块的 __init__.py ):

    app = Flask(__name__)
    app.config.from_object('config')
    

    在您的app模块中,创建 auth.py

    from flask import url_for, current_app, redirect, request
    from rauth import OAuth2Service
    
    import json, urllib2
    
    class OAuthSignIn(object):
        providers = None
    
        def __init__(self, provider_name):
            self.provider_name = provider_name
            credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name]
            self.consumer_id = credentials['id']
            self.consumer_secret = credentials['secret']
    
        def authorize(self):
            pass
    
        def callback(self):
            pass
    
        def get_callback_url(self):
            return url_for('oauth_callback', provider=self.provider_name,
                            _external=True)
    
        @classmethod
        def get_provider(self, provider_name):
            if self.providers is None:
                self.providers={}
                for provider_class in self.__subclasses__():
                    provider = provider_class()
                    self.providers[provider.provider_name] = provider
            return self.providers[provider_name]
    
    class GoogleSignIn(OAuthSignIn):
        def __init__(self):
            super(GoogleSignIn, self).__init__('google')
            googleinfo = urllib2.urlopen('https://accounts.google.com/.well-known/openid-configuration')
            google_params = json.load(googleinfo)
            self.service = OAuth2Service(
                    name='google',
                    client_id=self.consumer_id,
                    client_secret=self.consumer_secret,
                    authorize_url=google_params.get('authorization_endpoint'),
                    base_url=google_params.get('userinfo_endpoint'),
                    access_token_url=google_params.get('token_endpoint')
            )
    
        def authorize(self):
            return redirect(self.service.get_authorize_url(
                scope='email',
                response_type='code',
                redirect_uri=self.get_callback_url())
                )
    
        def callback(self):
            if 'code' not in request.args:
                return None, None, None
            oauth_session = self.service.get_auth_session(
                    data={'code': request.args['code'],
                          'grant_type': 'authorization_code',
                          'redirect_uri': self.get_callback_url()
                         },
                    decoder = json.loads
            )
            me = oauth_session.get('').json()
            return (me['name'],
                    me['email'])
    

    这将创建一个可以进行子类化的通用 OAuthSignIn 类 . Google子类从Google发布的信息列表中提取信息(采用JSON格式here) . 这是可以更改的信息,因此这种方法将确保它始终是最新的 . 这样做的一个限制是,如果在初始化Flask应用程序(导入模块)时服务器上没有Internet连接,则无法正确实例化 . 这几乎不应该是一个问题,但在配置数据库中存储最后已知的值以涵盖这种可能性是一个好主意 .

    最后,该类在 callback() 函数中返回 name, email 的元组 . Google实际上会返回更多信息,包括Google Profiles (如果有) . 检查 oauth_session.get('').json() 返回的字典以查看全部内容 . 如果在 authorize() 功能中扩展了范围(对于我的应用, email 就足够了),您可以通过Google API访问更多信息 .

    接下来,写下 views 将它们绑在一起:

    from flask.ext.login import login_user, logout_user, current_user, login_required
    
    @app.route('/authorize/<provider>')
    def oauth_authorize(provider):
        # Flask-Login function
        if not current_user.is_anonymous():
            return redirect(url_for('index'))
        oauth = OAuthSignIn.get_provider(provider)
        return oauth.authorize()
    
    @app.route('/callback/<provider>')
    def oauth_callback(provider):
        if not current_user.is_anonymous():
            return redirect(url_for('index'))
        oauth = OAuthSignIn.get_provider(provider)
        username, email = oauth.callback()
        if email is None:
            # I need a valid email address for my user identification
            flash('Authentication failed.')
            return redirect(url_for('index'))
        # Look if the user already exists
        user=User.query.filter_by(email=email).first()
        if not user:
            # Create the user. Try and use their name returned by Google,
            # but if it is not set, split the email address at the @.
            nickname = username
            if nickname is None or nickname == "":
                nickname = email.split('@')[0]
    
            # We can do more work here to ensure a unique nickname, if you 
            # require that.
            user=User(nickname=nickname, email=email)
            db.session.add(user)
            db.session.commit()
        # Log in the user, by default remembering them for their next visit
        # unless they log out.
        login_user(user, remember=True)
        return redirect(url_for('index'))
    

    最后,我的 /login 视图和模板使这一切成为现实:

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if g.user is not None and g.user.is_authenticated():
            return redirect(url_for('index'))
        return render_template('login.html',
                               title='Sign In')
    

    的login.html:

    {% extends "base.html" %}
    
    {% block content %}
    
        <div id="sign-in">
            <h1>Sign In</h1>
            <p>
            <a href={{ url_for('oauth_authorize', provider='google') }}><img src="{{ url_for('static', filename='img/sign-in-with-google.png') }}" /></a>
        </div>
    {% endblock %}
    

    确保向Google注册了正确的回叫地址,用户只需在登录页面上点击“使用Google登录”,即可注册并登录 .

  • 5

    我没有听起来那么难 . 基本上你需要遵循https://developers.google.com/accounts/docs/OAuth2WebServer指南,就是这样 . 为此,您还需要在https://code.google.com/apis/console/注册以生成凭据并注册您的链接 . 我使用简单的子域指向我的办公室IP,因为它只允许域 .

    对于用户登录/管理和会话,我使用了这个插件来烧瓶http://packages.python.org/Flask-Login/ - 会有一些基于此的代码 .

    首先是第一件事 - 索引视图:

    from flask import render_template
    from flask.ext.login import current_user
    from flask.views import MethodView
    
    from myapp import app
    
    
    class Index(MethodView):
        def get(self):
            # check if user is logged in
            if not current_user.is_authenticated():
                return app.login_manager.unauthorized()
    
            return render_template('index.html')
    

    所以在我们将验证用户之前,此视图不会打开 . 谈论用户 - 用户模型:

    from sqlalchemy.orm.exc import NoResultFound
    from sqlalchemy import Column, Integer, DateTime, Boolean, String
    
    from flask.ext.login import UserMixin
    from myapp.metadata import Session, Base
    
    
    class User(Base):
        __tablename__ = 'myapp_users'
    
        id = Column(Integer, primary_key=True)
        email = Column(String(80), unique=True, nullable=False)
        username = Column(String(80), unique=True, nullable=False)
    
        def __init__(self, email, username):
            self.email = email
            self.username = username
    
        def __repr__(self):
            return "<User('%d', '%s', '%s')>" \
                    % (self.id, self.username, self.email)
    
        @classmethod
        def get_or_create(cls, data):
            """
            data contains:
                {u'family_name': u'Surname',
                u'name': u'Name Surname',
                u'picture': u'https://link.to.photo',
                u'locale': u'en',
                u'gender': u'male',
                u'email': u'propper@email.com',
                u'birthday': u'0000-08-17',
                u'link': u'https://plus.google.com/id',
                u'given_name': u'Name',
                u'id': u'Google ID',
                u'verified_email': True}
            """
            try:
                #.one() ensures that there would be just one user with that email.
                # Although database should prevent that from happening -
                # lets make it buletproof
                user = Session.query(cls).filter_by(email=data['email']).one()
            except NoResultFound:
                user = cls(
                        email=data['email'],
                        username=data['given_name'],
                    )
                Session.add(user)
                Session.commit()
            return user
    
        def is_active(self):
            return True
    
        def is_authenticated(self):
            """
            Returns `True`. User is always authenticated. Herp Derp.
            """
            return True
    
        def is_anonymous(self):
            """
            Returns `False`. There are no Anonymous here.
            """
            return False
    
        def get_id(self):
            """
            Assuming that the user object has an `id` attribute, this will take
            that and convert it to `unicode`.
            """
            try:
                return unicode(self.id)
            except AttributeError:
                raise NotImplementedError("No `id` attribute - override get_id")
    
        def __eq__(self, other):
            """
            Checks the equality of two `UserMixin` objects using `get_id`.
            """
            if isinstance(other, UserMixin):
                return self.get_id() == other.get_id()
            return NotImplemented
    
        def __ne__(self, other):
            """
            Checks the inequality of two `UserMixin` objects using `get_id`.
            """
            equal = self.__eq__(other)
            if equal is NotImplemented:
                return NotImplemented
            return not equal
    

    UserMixin可能有问题,但我会处理后者 . 您的用户模型看起来会有所不同,只需使其与flask-login兼容即可 .

    那么剩下的就是 - 自我认证 . 我为 flask-login 设置了登录视图 'login' . Login 视图使用登录按钮呈现html,指向谷歌 - 谷歌重定向到 Auth 视图 . 如果只是登录用户的网站,应该可以将用户重定向到谷歌 .

    import logging
    import urllib
    import urllib2
    import json
    
    from flask import render_template, url_for, request, redirect
    from flask.views import MethodView
    from flask.ext.login import login_user
    
    from myapp import settings
    from myapp.models import User
    
    
    logger = logging.getLogger(__name__)
    
    
    class Login(BaseViewMixin):
        def get(self):
            logger.debug('GET: %s' % request.args)
            params = {
                'response_type': 'code',
                'client_id': settings.GOOGLE_API_CLIENT_ID,
                'redirect_uri': url_for('auth', _external=True),
                'scope': settings.GOOGLE_API_SCOPE,
                'state': request.args.get('next'),
            }
            logger.debug('Login Params: %s' % params)
            url = settings.GOOGLE_OAUTH2_URL + 'auth?' + urllib.urlencode(params)
    
            context = {'login_url': url}
            return render_template('login.html', **context)
    
    
    class Auth(MethodView):
        def _get_token(self):
            params = {
                'code': request.args.get('code'),
                'client_id': settings.GOOGLE_API_CLIENT_ID,
                'client_secret': settings.GOOGLE_API_CLIENT_SECRET,
                'redirect_uri': url_for('auth', _external=True),
                'grant_type': 'authorization_code',
            }
            payload = urllib.urlencode(params)
            url = settings.GOOGLE_OAUTH2_URL + 'token'
    
            req = urllib2.Request(url, payload)  # must be POST
    
            return json.loads(urllib2.urlopen(req).read())
    
        def _get_data(self, response):
            params = {
                'access_token': response['access_token'],
            }
            payload = urllib.urlencode(params)
            url = settings.GOOGLE_API_URL + 'userinfo?' + payload
    
            req = urllib2.Request(url)  # must be GET
    
            return json.loads(urllib2.urlopen(req).read())
    
        def get(self):
            logger.debug('GET: %s' % request.args)
    
            response = self._get_token()
            logger.debug('Google Response: %s' % response)
    
            data = self._get_data(response)
            logger.debug('Google Data: %s' % data)
    
            user = User.get_or_create(data)
            login_user(user)
            logger.debug('User Login: %s' % user)
            return redirect(request.args.get('state') or url_for('index'))
    

    因此,所有内容都分为两部分 - 一部分用于获取 _get_token 中的Google令牌 . 其他用于在 _get_data 中使用它并检索基本用户数据 .

    我的设置文件包含:

    GOOGLE_API_CLIENT_ID = 'myid.apps.googleusercontent.com'
    GOOGLE_API_CLIENT_SECRET = 'my secret code'
    GOOGLE_API_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'
    GOOGLE_OAUTH2_URL = 'https://accounts.google.com/o/oauth2/'
    GOOGLE_API_URL = 'https://www.googleapis.com/oauth2/v1/'
    

    请记住,视图必须附加url路径到应用程序,所以我使用这个 urls.py 文件,以便我可以更轻松地跟踪我的视图,并导入更少的东西到烧瓶应用程序创建文件:

    from myapp import app
    from myapp.views.auth import Login, Auth
    from myapp.views.index import Index
    
    
    urls = {
        '/login/': Login.as_view('login'),
        '/auth/': Auth.as_view('auth'),
        '/': Index.as_view('index'),
    }
    
    for url, view in urls.iteritems():
        app.add_url_rule(url, view_func=view)
    

    所有这些共同使得在Flask中使用Google授权 . 如果你复制粘贴它 - 它可能需要一些修补瓶登录文档和SQLAlchemy映射,但这个想法就在那里 .

  • 19

    试试Authomatic(我是该项目的维护者) . 它使用起来非常简单,适用于 any Python framework 并支持 16 OAuth 2.010 OAuth 1.0a 提供程序和 OpenID .

    以下是有关如何使用Google和 get his/her list of YouTube videos 对用户进行身份验证的简单示例:

    # main.py
    
    from flask import Flask, request, make_response, render_template
    from authomatic.adapters import WerkzeugAdapter
    from authomatic import Authomatic
    from authomatic.providers import oauth2
    
    
    CONFIG = {
        'google': {
            'class_': oauth2.Google,
            'consumer_key': '########################',
            'consumer_secret': '########################',
            'scope': oauth2.Google.user_info_scope + ['https://gdata.youtube.com'],
        },
    }
    
    app = Flask(__name__)
    authomatic = Authomatic(CONFIG, 'random secret string for session signing')
    
    
    @app.route('/login/<provider_name>/', methods=['GET', 'POST'])
    def login(provider_name):
        response = make_response()
    
        # Authenticate the user
        result = authomatic.login(WerkzeugAdapter(request, response), provider_name)
    
        if result:
            videos = []
            if result.user:
                # Get user info
                result.user.update()
    
                # Talk to Google YouTube API
                if result.user.credentials:
                    response = result.provider.access('https://gdata.youtube.com/'
                        'feeds/api/users/default/playlists?alt=json')
                    if response.status == 200:
                        videos = response.data.get('feed', {}).get('entry', [])
    
            return render_template(user_name=result.user.name,
                                   user_email=result.user.email,
                                   user_id=result.user.id,
                                   youtube_videos=videos)
        return response
    
    
    if __name__ == '__main__':
        app.run(debug=True)
    

    还有一个非常简单的Flask tutorial,它显示了如何通过Facebook和Twitter对用户进行身份验证,并与他们的API通信以阅读用户的新闻源 .

  • 37

    Flask-Dance是一个将Flask,Requests和OAuthlib链接在一起的新库 . 它有一个漂亮的API,它内置支持Google auth,along with a quickstart for how to get started . 试试看!

  • 0

    Flask-oauth可能是你现在最好的选择,因为我知道它不支持令牌刷新,但它可以与Facebook一起使用,我们使用它并且它是oauth 2.如果它不需要特定于烧瓶,你可能会看看request-oauth

  • 32

    看起来新模块Flask-Rauth就是这个问题的答案:

    Flask-Rauth是一个Flask扩展,允许您轻松地与OAuth 2.0,OAuth 1.0a和支持Ofly的应用程序进行交互 . [...]这意味着Flask-Rauth将允许Flask网站上的用户登录外部Web服务(即Twitter API,Facebook Graph API,GitHub等) .

    见:Flask-Rauth

  • 0

    不专门针对谷歌 - https://github.com/lepture/flask-oauthlib,它有一个如何在https://github.com/lepture/example-oauth2-server实现客户端和服务器的示例

  • 1

    由于oauth2client现已弃用,我建议蓝色建议 . Bruno Rocha's model OAuth2 Flask中的Google身份验证是使用lepture强大的Flask-OAuthlib(pip-installable)的一个很好的起点 . 我建议模仿,然后扩展以满足您的需求 .

相关问题