Использование Google OAuth2 с Flask
Может ли кто-нибудь указать мне полный пример аутентификации с учетными записями Google, используя OAuth2 и Flask, а не в App Engine?
Я пытаюсь предоставить пользователям доступ к Календарю Google, а затем использовать этот доступ для извлечения информации из календаря и дальнейшей обработки. Мне также нужно сохранить и позже обновить токены OAuth2.
Я просмотрел библиотеку Google oauth2client и может начать танцевать код авторизации, но я немного потерял оттуда. Глядя на платформу Google OAuth 2.0, я понимаю, что мне нужно запросить токен обновления и токен доступа, но предоставленные примеры в библиотеке предназначены только для App Engine и Django.
Я также пробовал использовать Flask OAuth module, который содержит ссылки на OAuth2, но я не вижу способа обменять код авторизации там либо.
Возможно, я мог бы передать код запроса, но предпочел бы использовать или адаптировать существующий модуль python, который упрощает запросы, правильно обрабатывает возможные ответы и, возможно, даже помогает хранить токены.
Есть ли такая вещь?
В другом ответе упоминается 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" на странице входа, и он зарегистрирует их и войдет в систему.
Я искал довольно много об использовании разных библиотек, но все они казались эфирным излишеством в некотором смысле (вы можете использовать его на любой платформе, но для этого вам нужна тонна кода), или документация не объяснила, что я хотел к. Короче говоря, я написал это с нуля, таким образом понимая процесс аутентификации истинного 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, но идея там есть.
Похожие вопросы
Дайте 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-Dance - это новая библиотека, которая связывает воедино Flask, Requests и OAuthlib. У него красивый API, встроенная поддержка аутентификации Google, а также краткое руководство по началу работы. Попробуйте!
Flask-oauth - это, вероятно, ваш лучший выбор прямо сейчас для флеш-специфического способа сделать это, насколько я знаю, он не поддерживает токен, но он будет работать с Facebook, мы используем его для этого, и это oauth 2 Если вам не нужна конкретная фляга, вы можете посмотреть на запросы-oauth
Похоже, что новый модуль Flask-Rauth является ответом на этот вопрос:
Flask-Rauth - это расширения Flask, которые позволяют вам легко взаимодействовать с OAuth 2.0, OAuth 1.0a и явно включенными приложениями. [...] Это означает, что Flask-Rauth позволит пользователям на вашем веб-сайте Flask войти в внешние веб-службы (т.е. API Twitter, Facebook Graph API, GitHub и т.д.).
Смотрите: Flask-Rauth
Не специально для google - https://github.com/lepture/flask-oauthlib, и в нем есть пример того, как реализовать клиент и сервер в https://github.com/lepture/example-oauth2-server
Поскольку oauth2client теперь устарел, я рекомендую, что предлагает bluemoon. Модель Bruno Rocha OAuth2 Аутентификация Google в Flask - хорошая отправная точка для использования lepture robust Flask-OAuthlib (доступно для печати). Я рекомендую подражать, а затем расширяться в соответствии с вашими потребностями.
Посмотрите другие вопросы по меткам python flask oauth oauth-2.0 или Задайте вопрос