program tip

Flask와 함께 Google OAuth2 사용

radiobox 2021. 1. 7. 07:51
반응형

Flask와 함께 Google OAuth2 사용


누구든지 App Engine이 아닌 OAuth2 및 Flask를 사용하여 Google 계정으로 인증하는 완전한 예제를 알려줄 수 있습니까?

사용자가 Google 캘린더에 대한 액세스 권한을 부여한 다음 해당 액세스를 사용하여 캘린더에서 정보를 검색하고 추가로 처리하도록하려고합니다. 또한 OAuth2 토큰을 저장하고 나중에 새로 고쳐야합니다.

Google의 oauth2client 라이브러리를 살펴 보았고 인증 코드를 검색하기 위해 춤을 출 수 있지만 거기에서 약간 잃어 버렸습니다. Google의 OAuth 2.0 Playground를 살펴보면 새로 고침 토큰과 액세스 토큰을 요청해야한다는 것을 알고 있지만 라이브러리에 제공된 예제는 App Engine 및 Django 전용입니다.

또한 OAuth2에 대한 참조를 포함 하는 Flask의 OAuth 모듈사용해 보았지만 인증 코드를 교환 할 방법이 없습니다.

요청을 직접 코딩 할 수는 있지만 요청을 쉽게 만들고 가능한 응답을 적절히 처리하며 토큰 저장을 지원하는 기존 Python 모듈을 사용하거나 조정하는 것을 훨씬 선호합니다.

그런 것이 있습니까?


또 다른 답변은 Flask-Rauth를 언급 하지만 사용 방법에 대해서는 자세히 설명하지 않습니다. 몇 가지 Google 관련 문제가 있지만 마침내 구현했으며 잘 작동합니다. Flask-Login과 통합하여 .NET과 같은 유용한 설탕으로 뷰를 장식 할 수 있습니다 @login_required.

여러 OAuth2 공급자를 지원할 수 있기를 원했기 때문에 코드의 일부는 일반적이며 여기 에서 Facebook 및 Twitter 에서 OAuth2를 지원하는 것에 대한 Miguel Grinberg의 훌륭한 게시물을 기반으로 합니다 .

먼저 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, emailcallback()기능을. Google은 실제로 가능한 경우 Google+ 프로필을 포함하여 더 많은 정보를 반환합니다. 에서 반환 된 사전을 검사하여 oauth_session.get('').json()모두 확인하십시오. 의 경우 authorize()기능 당신이 (내 앱, 범위를 확대 email충분하다), 당신은 구글 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')

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로 로그인"을 클릭하기 만하면됩니다. 그러면 등록하고 로그인합니다.


나는 다른 라이브러리를 사용하는 것에 대해 꽤 많이 검색했지만 그들 모두는 어떤 의미에서 에테르 과잉으로 보였거나 (어떤 플랫폼에서든 사용할 수 있지만 많은 코드가 필요합니다) 문서가 내가 원하는 것을 설명하지 못했습니다. 긴 이야기 짧게-나는 처음부터 작성하여 진정한 Google API 인증 프로세스를 이해했습니다. 소리만큼 어렵지는 않습니다. 기본적으로 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그 로그인보기에 대해 설정 한 것은입니다 'login'. Loginview는 google을 가리키는 로그인 버튼으로 html을 렌더링합니다 Auth. google은 보기로 리디렉션합니다 . 로그인 한 사용자만을위한 웹 사이트 인 경우 사용자를 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'))

따라서 모든 것이 두 부분으로 나뉩니다. 하나는 _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)

이 모든 것이 함께 Flask에서 작동하는 Google 인증을 만듭니다. 복사하여 붙여 넣으면 플라스크 로그인 문서 및 SQLAlchemy 매핑으로 약간의 수정이 필요할 수 있지만 아이디어는 있습니다.


부여 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와 대화하여 사용자의 뉴스 피드를 읽는 방법을 보여주는 매우 간단한 Flask 자습서 도 있습니다 .


Flask-Dance is a new library that links together Flask, Requests, and OAuthlib. It has a beautiful API, and it has builtin support for Google auth, along with a quickstart for how to get started. Give it a try!


I was able to port the accepted answer to use Requests-OAuthlib instead of Rauth. As of this writing, the package's last commit was on June 2019 and was currently use by 30K+ repositories.

To install, run:

$ pip install requests_oauthlib

Note, OAUTHLIB_RELAX_TOKEN_SCOPE environment variable must be set to True to suppress Scope has changed warning. On windows, this can be done by running:

$ 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"]

Requests-OAuthlib documentation can be found here https://requests-oauthlib.readthedocs.io/en/latest/index.html.


Flask-oauth is probably your best bet right now for a flask specific way to do it, as far as I know it doesn't support token refreshing but it will work with Facebook, we use it for that and it's oauth 2. If it doesn't need to be flask specific you might look at requests-oauth


It looks like the new module Flask-Rauth is the answer to this question:

Flask-Rauth is a Flask extensions that allows you to easily interact with OAuth 2.0, OAuth 1.0a, and Ofly enabled applications. [...] This means that Flask-Rauth will allow users on your Flask website to sign in to external web services (i.e. the Twitter API, Facebook Graph API, GitHub, etc).

See: Flask-Rauth


Not specifically for google -- https://github.com/lepture/flask-oauthlib and it has an example for how to implement the client and server at https://github.com/lepture/example-oauth2-server


As oauth2client is now deprecated, I recommend what bluemoon suggests. Bruno Rocha's model of OAuth2 Google authentication in Flask is a nice starting point for using lepture's robust Flask-OAuthlib (pip-installable). I recommend mimicking, then expanding to suit your needs.

ReferenceURL : https://stackoverflow.com/questions/9499286/using-google-oauth2-with-flask

반응형