在Flask中使用Google OAuth2

任何人都可以指出我使用OAuth2和Flask进行身份validation的完整示例,而不是 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身份validation信息添加到您应用的configuration中:

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

而当你创build你的应用程序(在我的情况下,该模块的__init__.py ):

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

在您的应用程序模块中,创buildauth.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']) 

这创build了一个可以分类的通用OAuthSignIn类。 Google子类从Google发布的信息列表中提取信息( 这里是JSON格式)。 这是可以改变的信息,所以这种方法将确保它始终是最新的。 其中一个限制是,如果在Flask应用程序初始化(模块导入)时,如果服务器上没有Internet连接,则不会正确实例化。 这应该几乎不会是一个问题,但是将最后一个已知值存储在configuration数据库中以涵盖这种可能性是一个好主意。

最后,这个类在callback()函数中返回一个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注册的,用户只需在login页面点击“使用Googlelogin”,就可以注册并login。

我已经search了很多关于使用不同的库的信息,但是从某种意义上来说,它们看起来都是过度的(你可以在任何平台上使用它,但是因为你需要大量的代码)或者文档没有解释我想要的东西。 长话短说 – 我从头开始写,从而了解真正的Google APIauthentication过程。 这听起来并不难。 基本上你需要遵循https://developers.google.com/accounts/docs/OAuth2WebServer准则,就是这样。; 为此,您还需要在https://code.google.com/apis/console/上注册以生成凭据并注册您的链接。; 我用简单的子域指向我的办公室IP,因为它只允许域名。

对于用户login/pipe理和会话,我已经使用这个插件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') 

所以这个观点将不会打开,直到我们将authentication用户。 说说用户 – 用户模式:

 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可能有问题,但我会处理后者。 你的用户模型看起来不一样,只要使它与烧瓶login兼容即可。

那剩下的是什么 – authentication是自己的。 我为flask-login设置login视图是'login'Login视图呈现与loginbutton,指向谷歌的HTML – 谷歌redirect到身份Auth视图。 它应该是可能的只是redirect用户谷歌的情况下,只有login用户的网站。

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

请记住,视图必须具有连接到应用程序的URLpath,所以我已经使用这个urls.py文件,以便我可以更容易地跟踪我的视图,并导入更less的东西到瓶子应用程序创build文件:

 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授权成为可能。 如果你复制粘贴它 – 它可能需要一些补丁与烧瓶login文档和SQLAlchemy映射,但想法是在那里。

给Authomatic一个尝试(我是该项目的维护者)。 它使用起来非常简单,适用于任何Python框架,并支持16 个OAuth 2.0,10个OAuth 1.0a提供商和OpenID

以下是一个简单的例子,介绍如何使用Google对用户进行身份validation,并获取他/她的YouTubevideo列表

 # 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和Twitterauthentication用户,并与他们的API交谈来阅读用户的新闻传播。

Flask-oauth可能是你现在最好的select,因为我知道它不支持令牌刷新,但是它会和Facebook一起工作,我们用它来做这件事,它是oauth 2.如果它不需要特定的瓶子,你可以看一下request-oauth

看起来新模块Flask-Rauth是这个问题的答案:

Flask-Rauth是一个Flask扩展程序,允许您轻松与OAuth 2.0,OAuth 1.0a和已启用的应用程序进行交互。 这意味着Flask-Rauth将允许您的Flask网站上的用户login到外部Web服务(即Twitter API,Facebook Graph API,GitHub等)。

见: Flask-Rauth

不是专门为谷歌 – https://github.com/lepture/flask-oauthlib ,它有一个如何实现客户端和服务器在https://github.com/lepture/example-oauth2-server

由于oauth2client现在已经被弃用了,我推荐bluemoon所暗示的。 Bruno Rocha在Flask中的OAuth2 Google身份validation模型是使用lepture强大的Flask-OAuthlib (pip-installable)的一个不错的起点。 我build议模仿,然后扩大以满足您的需求。