Использование Google OAuth2 с Flask - PullRequest
54 голосов
/ 29 февраля 2012

Может ли кто-нибудь указать мне полный пример аутентификации с помощью учетных записей Google с использованием OAuth2 и Flask, а не в App Engine?

Я пытаюсь предоставить пользователям доступ к Календарю Googleи затем используйте этот доступ для извлечения информации из календаря и дальнейшей ее обработки.Мне также нужно сохранить и позже обновить токены OAuth2.

Я посмотрел библиотеку Google oauth2client и могу начать танцевать, чтобы получить код авторизации, но я немного растерялсяоттуда.Глядя на Google OAuth 2.0 Playground, я понимаю, что мне нужно запросить токен обновления и токен доступа, но приведенные в библиотеке примеры предназначены только для App Engine и Django.

Я также пытался использовать Flask'sМодуль OAuth , который содержит ссылки на OAuth2, но я также не вижу возможности обменивать там код авторизации.

Я мог бы, вероятно, вручную закодировать запросы, но очень предпочел бы использовать или адаптироватьсуществующий модуль python, который упрощает запросы, правильно обрабатывает возможные ответы и, возможно, даже помогает хранить токены.

Есть ли такая вещь?

Ответы [ 8 ]

39 голосов
/ 14 апреля 2015

В другом ответе упоминается Flask-Rauth , но не рассказывается о том, как его использовать.Есть несколько специфичных для Google ошибок, но я наконец-то реализовал их, и они хорошо работают.Я интегрирую его с Flask-Login, чтобы я мог украсить свои взгляды полезным сахаром, таким как @login_required.

Я хотел иметь возможность поддерживать несколько поставщиков OAuth2, поэтому часть кода является общей и основана на превосходном посте Мигеля Гринберга о поддержке OAuth2 с Facebook и Twitter здесь .

Во-первых, добавьте вашу конкретную информацию для аутентификации 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')

В модуле приложения создайте 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 здесь ).Это информация, которая может быть изменена, поэтому этот подход гарантирует, что она всегда актуальна.Одним из ограничений этого является то, что если интернет-соединение недоступно на вашем сервере во время инициализации приложения Flask (импортированный модуль), оно не будет создано правильно.Это почти никогда не должно быть проблемой, но сохранение последних известных значений в базе данных конфигурации для покрытия этой возможности является хорошей идеей.

Наконец, класс возвращает кортеж name, email в функции callback(),Google фактически возвращает намного больше информации, включая профиль Google+, если он доступен.Просмотрите словарь, возвращенный oauth_session.get('').json(), чтобы увидеть все это.Если в функции authorize() вы расширяете область действия (для моего приложения достаточно email), вы можете получить доступ к еще большей информации через API Google.

Далее, напишите представления, чтобы связать все это вместе:

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» на странице входа, и он зарегистрирует их и войдет в систему.

32 голосов
/ 16 октября 2012

Я довольно много искал об использовании разных библиотек, но все они в некотором смысле казались излишним эфиром (вы можете использовать его на любой платформе, но для этого вам нужно тонны кода), или документация не объяснила, чего я хотел к. Короче говоря - я написал это с нуля, таким образом, понимая процесс аутентификации истинного Google API. Это не так сложно, как кажется. По сути, вы должны следовать https://developers.google.com/accounts/docs/OAuth2WebServer рекомендациям и все. Для этого вам также необходимо зарегистрироваться на https://code.google.com/apis/console/, чтобы создать учетные данные и зарегистрировать ваши ссылки. Я использовал простой поддомен, указывающий на IP-адрес моего офиса, поскольку он разрешает только домены.

Для входа в систему / управления пользователями и сеансов я использовал этот плагин для flask 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 с кнопкой входа в систему, которая указывает на Google - Google перенаправляет на Auth просмотра. Должна быть возможность просто перенаправить пользователя в Google, если он только для зарегистрированных пользователей.

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

Таким образом, все разделено на две части - одну для получения токена Google в _get_token. Другое для его использования и извлечения основных пользовательских данных в _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)

Все это вместе делает работу авторизации Google во Flask. Если вы копируете и вставляете его - это может потребовать внесения исправлений в документацию о входе в систему флакона и сопоставления SQLAlchemy, но идея здесь есть.

19 голосов
/ 24 февраля 2014

Дайте Authomatic попробовать (я поддерживаю этот проект).Он очень прост в использовании, работает с любой платформой Python и поддерживает 16 OAuth 2.0 , 10 OAuth 1.0a провайдеров и OpenID .

Вот простой пример того, как аутентифицировать пользователя с помощью Google и получить его / ее список видео на YouTube :

# 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 , который показывает, как аутентифицировать пользователя через Facebook и Twitter и общаться с его API, чтобы читать новостные ленты пользователя.

9 голосов
/ 10 февраля 2018

Flask-Dance - это новая библиотека, которая связывает воедино Flask, Requests и OAuthlib. У него красивый API и встроенная поддержка аутентификации Google , а также краткое руководство по началу работы . Попробуйте!

1 голос
/ 23 февраля 2014

Похоже, что новый модуль Flask-Rauth является ответом на этот вопрос:

Flask-Rauth - это расширение Flask, которое позволяет вам легко взаимодействовать с OAuth 2.0, OAuth 1.0a иЧасто включенные приложения.[...] Это означает, что Flask-Rauth позволит пользователям вашего веб-сайта Flask входить во внешние веб-службы (например, API Twitter, API Graph Facebook, GitHub и т. Д.).

См.: Колба-Раут

1 голос
/ 03 марта 2012

Flask-oauth - это, вероятно, ваша лучшая ставка на данный момент для конкретного способа сделать флягу, насколько я знаю, он не поддерживает обновление токена, но он будет работать с Facebook, мы используем его для этого, и это oauth 2 Если вам не нужна конкретная информация о колбе, вы можете посмотреть на запросы: oauth

0 голосов
/ 18 августа 2017

Поскольку oauth2client устарел, я рекомендую то, что предлагает bluemoon. Модель Бруно Роча проверки подлинности OAuth2 Google во Flask - хорошая отправная точка для использования надежного Flask-OAuthlib (устанавливается с помощью pip).Я рекомендую имитировать, а затем расширять в соответствии с вашими потребностями.

0 голосов
/ 27 апреля 2016

Не специально для Google - https://github.com/lepture/flask-oauthlib и в нем есть пример реализации клиента и сервера на https://github.com/lepture/example-oauth2-server

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...