在Flask中使用Google OAuth2
任何人都可以指出我使用OAuth2和Flask进行身份验证的完整示例,而不是App Engine?
我试图让用户授予Google日历访问权限,然后使用该访问权限从日历中检索信息并进一步处理。 我还需要存储并稍后刷新OAuth2令牌。
我查看了Google的oauth2client库,并且可以开始跳舞以检索授权码,但是我有点失落。 查看Google的OAuth 2.0 Playground我明白我需要请求刷新令牌和访问令牌,但库中提供的示例仅适用于App Engine和Django。
我也尝试使用包含对OAuth2的引用的Flask的OAuth模块,但我没有看到任何交换授权代码的方法。
我可能会手动编写请求代码,但会更愿意使用或修改现有的使请求容易的python模块,正确处理可能的响应,甚至可能协助存储令牌。
有这样的事吗?
另一个答案提到了Flask-Rauth,但没有详细说明如何使用它。 有几个谷歌特有的陷阱,但我最终实现了它,它运作良好。 我将它与Flask-Login整合在一起,这样我就可以像@login_required
一样用有用的糖来装饰我的视图。
我希望能够支持多个OAuth2提供程序,因此部分代码是通用的,并且基于Miguel Grinberg关于在此处使用Facebook和Twitter支持OAuth2的出色文章。
首先,将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应用程序初始化(模块导入)时,您的服务器上没有可用的Internet连接,它将不会正确实例化。 这应该几乎不会是一个问题,但将最后一个已知值存储在配置数据库中以涵盖这种可能性是个不错的主意。
最后,这个类在callback()
函数中返回一个name, email
为tuple的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')
的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兼容即可。
所以剩下的 - 认证是自己的。 我为flask-login
设置登录视图是'login'
。 Login
视图呈现与登录按钮,指向谷歌的HTML - 谷歌重定向到身份Auth
视图。 应该可能只是将用户重定向到谷歌,以防其登录用户的网站。
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)
还有一个非常简单的Flask教程,它显示了如何通过Facebook和Twitter认证用户,并与他们的API交谈以阅读用户的新闻传播。
链接地址: http://www.djcxy.com/p/63041.html上一篇: Using Google OAuth2 with Flask
下一篇: Child elements overflow is hidden in parent with overflow: hidden