Использование Google OAuth2 с колбой


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

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

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

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

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

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

8 52

8 ответов:

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

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

во-первых, добавьте свою конкретную информацию аутентификации 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 на . Google фактически возвращает гораздо больше информации, в том числе профиль Google+, если он доступен. Проверьте словарь, возвращенный oauth_session.get('').json() чтобы увидеть все это. Если в authorize() функция вы расширяете область (для моего приложения,email достаточно), вы можете получить доступ к еще большей информации через Google API.

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

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

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

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

для входа пользователя / управления и сессий я использовал этот плагин для 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 с кнопкой входа, которая указывает на Гугл - Гугл перенаправляет на 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-путь, прикрепленный к приложению, поэтому я использую это так что я может отслеживать мои взгляды более легко и импортировать меньше материала в файл создания приложения flask:

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-login documentation и SQLAlchemy mappings, но идея есть.

дать Authomatic попробовать (я координатор этого проекта). Он очень прост в использовании, работает с любой Python framework и обслуживает 16 OAuth 2.0, 10 OAuth 1.0 a поставщики и 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)

существует также очень простой фляга учебник который показывает, как аутентифицировать пользователя с помощью Facebook и Twitter и поговорить с их API, чтобы прочитать ленты новостей пользователя.

Фляга-Танец это новая библиотека, которая связывает вместе колбу, запросы и OAuthlib. Он имеет красивый API, и он имеет встроенную поддержку Google auth,вместе с кратким руководством о том, как начать работу. Дайте ему попробовать!

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

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

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

посмотреть: Фляга-Rauth

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

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