webentwicklung-frage-antwort-db.com.de

Google OAuth2 mit Flask verwenden

Kann sich jemand auf ein vollständiges Beispiel für die Authentifizierung bei Google-Konten mit OAuth2 und Flask und not in App Engine beziehen?

Ich versuche, den Nutzern den Zugriff auf Google Kalender zu ermöglichen, und diesen Zugriff dann verwenden, um Informationen aus dem Kalender abzurufen und weiter zu verarbeiten. Ich muss auch die OAuth2-Token speichern und später aktualisieren.

Ich habe mir die oauth2client -Bibliothek von Google angesehen und kann den Tanz dazu veranlassen, den Autorisierungscode abzurufen, aber da bin ich ein wenig verloren. Beim Betrachten des OAuth 2.0 Playground von Google verstehe ich, dass ich das Aktualisierungstoken und das Zugriffstoken anfordern muss. Die in der Bibliothek enthaltenen Beispiele beziehen sich jedoch nur auf App Engine und Django.

Ich habe auch versucht, Flaskes OAuth-Modul zu verwenden, das Verweise auf OAuth2 enthält, aber ich sehe auch keine Möglichkeit, den Autorisierungscode dort auszutauschen.

Ich könnte wahrscheinlich die Anfragen per Hand codieren, würde aber lieber ein vorhandenes Python-Modul verwenden oder anpassen, das Anfragen einfach macht, mögliche Antworten richtig behandelt und möglicherweise sogar die Speicherung von Token unterstützt.

Gibt es so etwas?

53
emning

Eine andere Antwort erwähnt Flask-Rauth , geht aber nicht näher auf die Verwendung ein. Es gibt ein paar Google-spezifische Gotchas, aber ich habe es endlich implementiert und es funktioniert gut. Ich baue es mit Flask-Login ein, damit ich meine Ansichten mit nützlichem Zucker wie @login_required schmücken kann. 

Ich wollte mehrere OAuth2-Anbieter unterstützen können, daher ist ein Teil des Codes generisch und basiert auf Miguel Grinbergs hervorragendem Beitrag zur Unterstützung von OAuth2 mit Facebook und Twitter hier .

Fügen Sie zunächst Ihre spezifischen Google-Authentifizierungsinformationen von Google zur Konfiguration Ihrer App hinzu:

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
        }
}

Und wenn Sie Ihre App erstellen (in meinem Fall der __init__.py des Moduls):

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

Erstellen Sie in Ihrem App-Modul 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'])

Dadurch wird eine generische OAuthSignIn-Klasse erstellt, die untergeordnet werden kann. Die Google-Unterklasse bezieht ihre Informationen aus der von Google veröffentlichten Informationsliste (im JSON-Format hier ). Hierbei handelt es sich um Informationen, die sich ändern können. Auf diese Weise wird sichergestellt, dass sie stets auf dem neuesten Stand sind. Eine Einschränkung dabei ist, dass, wenn zum Zeitpunkt der Initialisierung der Flask-Anwendung (das importierte Modul) keine Internetverbindung auf Ihrem Server verfügbar ist, diese nicht korrekt instanziiert wird. Dies sollte fast nie ein Problem sein, aber das Speichern der zuletzt bekannten Werte in der Konfigurationsdatenbank, um diese Eventualität abzudecken, ist eine gute Idee.

Schließlich gibt die Klasse einen Tupel von name, email in der Funktion callback() zurück. Google gibt tatsächlich viel mehr Informationen zurück, einschließlich des Google+ Profils, sofern verfügbar. Sehen Sie sich das von oauth_session.get('').json() zurückgegebene Wörterbuch an, um alles zu sehen . Wenn Sie in der Funktion authorize() den Umfang erweitern (für meine App ist email ausreichend), können Sie über die Google-API auf noch mehr Informationen zugreifen.

Schreiben Sie als Nächstes die views, um alles miteinander zu verbinden:

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

Zum Schluss meine /login-Ansicht und -Vorlage, um alles möglich zu machen:

@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 %}

Vergewissern Sie sich, dass die richtigen Rückrufadressen bei Google registriert sind, und der Benutzer muss auf Ihrer Anmeldeseite einfach auf "Bei Google anmelden" klicken. Er wird sie registrieren und anmelden.

37
Aaron D

Ich habe ziemlich viel über die Verwendung verschiedener Bibliotheken gesucht, aber alle schienen in gewissem Sinne etwas übertrieben zu sein (Sie können es auf jeder Plattform verwenden, aber dafür benötigen Sie eine Menge Code) oder die Dokumentation erklärte nicht, was ich wollte. Um es kurz zu machen: Ich habe es von Grund auf geschrieben und verstehe so den Prozess der Authentifizierung der wahren Google-API. Es ist nicht so schwer wie es klingt. Grundsätzlich müssen Sie die https://developers.google.com/accounts/docs/OAuth2WebServer - Richtlinien befolgen und das war's. Dazu müssen Sie sich auch unter https://code.google.com/apis/console/ registrieren, um Anmeldeinformationen zu erstellen und Ihre Links zu registrieren. Ich habe einfache Subdomains verwendet, die auf meine Büro-IP verweisen, da sie nur Domänen zulässt.

Für Benutzeranmeldung verwaltung und Sitzungen habe ich dieses Plugin für flask// http://packages.python.org/Flask-Login/ - verwendet. Daraufhin wird etwas Code erstellt.

Als erstes also - Indexansicht:

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

diese Ansicht wird also erst geöffnet, wenn wir den Benutzer authentifiziert haben . Sprechen über Benutzer - Benutzermodell:

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):
    __table= '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

Mit UserMixin ist wahrscheinlich etwas nicht in Ordnung, aber ich werde mich mit letzterem beschäftigen. Ihr Benutzermodell sieht anders aus, macht es nur mit dem Flaschen-Login kompatibel.

Was also noch übrig ist - die Authentifizierung selbst. Ich habe für flask-login festgelegt, dass die Login-Ansicht 'login' ist. Login view rendert html mit der Anmeldeschaltfläche, die darauf hinweist, dass google google auf Auth view umleitet. Es sollte möglich sein, den Benutzer nur auf google umzuleiten, falls die Website nur für angemeldete Benutzer verwendet wird.

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

Also ist alles in zwei Teile aufgeteilt - einen, um google token in _get_token zu erhalten. Andere zur Verwendung und zum Abrufen grundlegender Benutzerdaten in _get_data.

Meine Einstellungsdatei enthält:

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

Denken Sie daran, dass für Ansichten für Ansichten ein URL-Pfad mit der App verbunden sein muss. Ich verwende diese urls.py-Datei, damit ich meine Ansichten leichter verfolgen und weniger Material in die App-Erstellungsdatei importieren kann:

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)

All dies zusammen macht die Google-Autorisierung in Flask. Wenn Sie es kopieren, fügen Sie es ein - es kann einige Korrekturen mit der Dokumentation für die Kolbenanmeldung und SQLAlchemy-Zuordnungen erfordern, aber die Idee ist da.

32
JackLeo

Gib Authomatic einen Versuch (ich bin der Betreuer dieses Projekts). Es ist sehr einfach zu verwenden, arbeitet mit jedem Python-Framework zusammen und unterstützt 16 OAuth 2.0, 10 OAuth 1.0a Provider und OpenID.

Hier ist ein einfaches Beispiel, wie man einen Nutzer bei Google authentifiziert und seine YouTube-Videoliste erhält:

# 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 __== '__main__':
    app.run(debug=True)

Es gibt auch ein sehr einfaches Flask-Tutorial , das zeigt, wie ein Benutzer von Facebook und Twitter authentifiziert wird und mit seinen APIs die Newsfeeds des Benutzers lesen kann.

19
Peter Hudec

Flask-Dance ist eine neue Bibliothek, die Flask, Requests und OAuthlib miteinander verbindet. Es hat eine schöne API, und es hat eine eingebaute Unterstützung für Google auth, zusammen mit einem Schnelleinstieg für den Einstieg . Versuche es!

5
singingwolfboy

Ich konnte die akzeptierte Antwort portieren, um Requests-OAuthlib anstelle von Rauth zu verwenden. Zum jetzigen Zeitpunkt war das letzte Commit des Pakets im Juni 2019 und wurde derzeit von über 30.000 Repositorys verwendet.

Führen Sie zum Installieren Folgendes aus:

$ pip install requests_oauthlib

Hinweis, OAUTHLIB_RELAX_TOKEN_SCOPE Umgebungsvariable muss auf True gesetzt werden, um zu unterdrücken. Bereich hat Warnung geändert . Unter Windows können Sie dazu Folgendes ausführen:

$ set OAUTHLIB_RELAX_TOKEN_SCOPE=1
...
from requests_oauthlib import OAuth2Session
from urllib.request import urlopen


class GoogleSignIn(OAuthSignIn):
    openid_url = "https://accounts.google.com/.well-known/openid-configuration"

    def __init__(self):
        super(GoogleLogin, self).__init__("google")
        self.openid_config = json.load(urlopen(self.openid_url))
        self.session = OAuth2Session(
            client_id=self.consumer_id,
            redirect_uri=self.get_callback_url(),
            scope=self.openid_config["scopes_supported"]
        )

    def authorize(self):
        auth_url, _ = self.session.authorization_url(
            self.openid_config["authorization_endpoint"])
        return redirect(auth_url)

    def callback(self):
        if "code" not in request.args:
            return None, None

        self.session.fetch_token(
            token_url=self.openid_config["token_endpoint"],
            code=request.args["code"],
            client_secret=self.consumer_secret,
        )

        me = self.session.get(self.openid_config["userinfo_endpoint"]).json()
        return me["name"], me["email"]

Die Dokumentation zu Requests-OAuthlib finden Sie hier https://requests-oauthlib.readthedocs.io/en/latest/index.html .

2
bertdida

Flask-oauth ist wahrscheinlich die beste Wahl für eine flaskenspezifische Methode. Soweit ich weiß, wird die Aktualisierung von Token nicht unterstützt, aber es funktioniert mit Facebook Es muss nicht unbedingt spezifisch für die Flasche sein 

1
bluemoon

Anscheinend ist das neue Modul Flask-Rauth die Antwort auf diese Frage:

Flask-Rauth ist eine Flask-Erweiterung, mit der Sie problemlos mit OAuth 2.0, OAuth 1.0a und Ofly-fähigen Anwendungen interagieren können. [...] Dies bedeutet, dass Flask-Rauth Benutzern auf Ihrer Flask-Website die Möglichkeit bietet, sich bei externen Webservices (z. B. der Twitter-API, der Facebook Graph-API, GitHub usw.) anzumelden.

Siehe: Flask-Rauth

1
emning

Nicht speziell für google - https://github.com/lepture/flask-oauthlib . Es enthält ein Beispiel für die Implementierung von Client und Server unter https://github.com/lepture/example -oauth2-server

0
Andrei Sura

Da der oauth2client jetzt veraltet ist, empfehle ich, was bluemoon vorschlägt. Bruno Rochas Modell von OAuth2 Die Google-Authentifizierung in Flask ist ein guter Ausgangspunkt für die Verwendung von Leptures robustem Flask-OAuthlib (pip-installable). Ich empfehle das Nachahmen und dann das Erweitern nach Ihren Bedürfnissen.

0
user2901351