Подтвердить что ты не робот

Использование Google OAuth2 с Flask

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

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

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

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

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

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

4b9b3361

Ответ 1

В другом ответе упоминается 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() словарь, возвращенный 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" на странице входа, и он зарегистрирует их и войдет в систему.

Ответ 2

Я искал довольно много об использовании разных библиотек, но все они казались эфирным излишеством в некотором смысле (вы можете использовать его на любой платформе, но для этого вам нужна тонна кода), или документация не объяснила, что я хотел к. Короче говоря, я написал это с нуля, таким образом понимая процесс аутентификации истинного API Google. Это не так сложно, как кажется. В основном вам нужно следовать 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'[email protected]',
            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, что логин - это 'login'. Login view отображает 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, но идея там есть.

Ответ 3

Дайте 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)

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

Ответ 5

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

Ответ 6

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

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

Смотрите: Flask-Rauth

Ответ 8

Поскольку oauth2client теперь устарел, я рекомендую, что предлагает bluemoon. Модель Bruno Rocha OAuth2 Аутентификация Google в Flask - хорошая отправная точка для использования lepture robust Flask-OAuthlib (доступно для печати). Я рекомендую подражать, а затем расширяться в соответствии с вашими потребностями.