另一个答案提到了Flask-Rauth,但是没有详细介绍如何使用它。有一些特定于Google的陷阱,但是我终于实现了它,并且效果很好。我将其与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连接,则将无法正确实例化它。这几乎永远不会成为问题,但是在配置数据库中存储最新的值以解决这种情况是个好主意。
最后,该类name, email
在callback()
函数中返回一个元组。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登录”,它将进行注册并登录。