当前位置: 首页 > 面试题库 >

将Google OAuth2与Flask结合使用

聂和宜
2023-03-14
问题内容

谁能给我指出一个完整的示例,而 不是 在App Engine上使用OAuth2和Flask对Google帐户进行身份验证?

我试图让用户授予对Google日历的访问权限,然后使用该访问权限从日历中检索信息并对其进行进一步处理。我还需要存储并稍后刷新OAuth2令牌。

我看过Google的oauth2client库,可以开始检索授权代码了,但是我从那里有点迷失了。通过查看Google的OAuth
2.0游乐场,我了解到我需要请求刷新令牌和访问令牌,但是该库中提供的示例仅适用于App Engine和Django。

我也尝试过使用Flask的OAuth模块,该模块包含对OAuth2的引用,但是我也看不到任何交换授权代码的方法。

我可能可以手工编写请求的代码,但更喜欢使用或改编现有的python模块,该模块使请求变得容易,正确处理可能的响应,甚至可以帮助存储令牌。

有这样的事吗?


问题答案:

另一个答案提到了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, emailcallback()函数中返回一个元组。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登录”,它将进行注册并登录。



 类似资料:
  • 问题内容: 我在SO上发现了其他几个有关JavaMail API和通过SMTP服务器发送邮件的问题,但是没有一个使用TLS安全性进行讨论。我正在尝试使用JavaMail通过我的工作SMTP邮件服务器向我发送状态更新,但是它需要TLS,而且我在网上找不到任何有关如何使用JavaMail访问需要TLS加密的SMTP服务器的示例。有人能帮忙吗? 问题答案: 实际上,我们的产品中确实有一些通知代码,如果有

  • 我已经阅读了一些关于SaaS/PaaS/IaaS的信息,我在想: 如果我提供了一个SaaS应用程序,我的客户能否使用PaaS开发和部署他自己的应用程序,PaaS将使用一些API与我的SaaS应用程序交互?还是我对云服务的理解有误?

  • 问题内容: TextMate似乎使用了我假定的内置Python版本(sys.path不起作用)。您如何配置它以使用3.1?我已经安装了3.1软件包,并且可以将IDLE用于交互式会话,但是现在需要使用TextMate。 谢谢 问题答案: TextMate使用变量的值来查找Python解释器的路径。一个好的解决方案是利用TextMate的能力来定义变量(例如基于每个项目): 打开一个新的或现有的Tex

  • 问题内容: 我想使用并且在本节中非常基础,我需要一些帮助,并且在网上之前对此主题进行了研究,但是没有令人满意的解决方案。 1-如何FFmpeg通过使用。(我不想使用Ubuntu,请不要建议。) 2-如何通过插入命令? 最后通知:我使用和。 任何建议,将不胜感激。 问题答案: 如果您需要进一步的帮助,请确切说明您要实现的目标以及不起作用的目标… 更新-根据评论: 要在Android上通过命令行使用f

  • 我严格遵循了一个教程,但我似乎无法让它发挥作用。本教程位于IDE的非模块化JavaFX和Intellij部分:https://openjfx.io/openjfx-docs/#install-爪哇 以下是我在尝试运行默认Intellij Idea JavaFX项目时收到的错误消息: 通向jdk-11.0.1和javafx-sdk-11.0.1的路径: C:\Program Files\Java J

  • 我有一个非常简单的场景:测试任何一对长度为10的随机字符串作为参数传递给一个case类对(一个正在测试的自定义字符串)应该是相同的。 但是,当我从sbt运行我只得到这个编译错误: 斯卡拉切克烹饪书中的一个注解说 该错误消息表明,我们的属性检查没有按照布尔逻辑进行评估 我希望内部块最终应该返回布尔值。如果您能指出我在这个实现中理解基于属性的测试或使用Inside trait时缺少什么,我将不胜感激。