首页 > 编程问答 >在 Flask 中使用 Google API 进行“注册”和“登录”

在 Flask 中使用 Google API 进行“注册”和“登录”

时间:2024-07-23 08:51:19浏览次数:13  
标签:python flask google-cloud-platform

我正在尝试使用 Google API 使用 Flask 在 Python 中编写登录系统。有用。然而,当用户已经注册并尝试第二次登录时,选择电子邮件后,系统会继续再次请求访问权限,而不是仅仅允许用户进入。我相信用户每次尝试登录时阅读“确保您信任此应用程序”都会有点不舒服,而在任何其他应用程序中,只需选择电子邮件即可授予访问权限,而无需再次授权...| ||截图

我的源代码如下:

这是我第一次编程,请原谅我写得不好的代码...

import os
import pathlib

import requests
from flask import Flask, session, abort, redirect, request, render_template
from google.oauth2 import id_token
from google_auth_oauthlib.flow import Flow
from pip._vendor import cachecontrol
import google.auth.transport.requests

from database.db import PostgreSQLConnection
from object.user import UserObject
from model.user import UserModel

db = PostgreSQLConnection()

app = Flask(__name__)
app.secret_key = "123"

os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

GOOGLE_CLIENT_ID = "123"
client_secrets_file = os.path.join(pathlib.Path(__file__).parent, "client_secret.json")

flow = Flow.from_client_secrets_file(
    client_secrets_file=client_secrets_file,
    scopes=["https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email",
            "openid"],
    redirect_uri="http://127.0.0.1:5000/callback"
)


def login_is_required(function):
    def wrapper(*args, **kwargs):
        if "google_id" not in session:
            return abort(401)  # Authorization required
        else:
            return function()

    return wrapper


@app.route("/login")
def login():
    authorization_url, state = flow.authorization_url()
    session["state"] = state
    return redirect(authorization_url)


@app.route("/callback")
def callback():
    flow.fetch_token(authorization_response=request.url)

    if not session["state"] == request.args["state"]:
        abort(500)  # State does not match!

    credentials = flow.credentials
    request_session = requests.session()
    cached_session = cachecontrol.CacheControl(request_session)
    token_request = google.auth.transport.requests.Request(session=cached_session)

    id_info = id_token.verify_oauth2_token(
        id_token=credentials._id_token,
        request=token_request,
        audience=GOOGLE_CLIENT_ID
    )

    session["google_id"] = id_info.get("sub")
    session["email"] = id_info.get("email")
    session["given_name"] = id_info.get("given_name")
    session["family_name"] = id_info.get("family_name")

    if UserModel.get_user(db, session["google_id"]):
        print('Usuário:', session["email"])
    else:
        print('Usuário novo:', session["email"])
        user = UserObject(session["google_id"], session["email"], session["given_name"], session["family_name"])
        UserModel.add_new_user(db, user)

    return redirect("/home")


@app.route("/logout")
def logout():
    session.clear()
    return redirect("/")


@app.route("/")
def index():
    return render_template('login.html')


@app.route("/home")
@login_is_required
def home():
    return render_template('home.html', name=session['given_name'])


if __name__ == "__main__":
    app.run()

我在Google上搜索但没有找到解决方案。

I searched all over Google but didn't find the solution.


这个行为是预料之中的。当你使用 OAuth 2.0 进行授权时,并且用户已经授权你的应用访问他们的 Google 账户,你通常不需要再次请求访问权限。 但是,只有当你使用 offline_access 作用域请求刷新令牌时,才会发生这种情况。

你的代码没有请求 offline_access 作用域,所以每次用户登录时,你的应用都会从 Google 获取一个新的访问令牌,这需要用户重新授权。

为了解决这个问题,你需要:

  1. 请求 offline_access 作用域: 将其添加到你的 scopes 列表中:
scopes=["https://www.googleapis.com/auth/userinfo.profile", 
        "https://www.googleapis.com/auth/userinfo.email",
        "openid", 
        "offline_access"],
  1. 存储并使用刷新令牌: 当用户第一次授权你的应用时,Google 会发送一个刷新令牌。你需要将此令牌存储在安全的地方(例如数据库中)。在后续登录中,使用刷新令牌获取新的访问令牌,而无需用户重新授权。

这里是一个更新后的 callback 函数,它展示了如何存储和使用刷新令牌:

@app.route("/callback")
def callback():
    flow.fetch_token(authorization_response=request.url)

    if not session["state"] == request.args["state"]:
        abort(500)  # State does not match!

    credentials = flow.credentials

    # 存储刷新令牌
    if credentials.refresh_token:
        session['refresh_token'] = credentials.refresh_token
        # 你需要将刷新令牌存储在数据库中,以便在用户下次登录时使用它

    # ... rest of your code ... 

然后,在你需要访问用户信息的路由中,你可以检查是否存在刷新令牌,如果存在,则使用它来获取新的访问令牌:

@app.route("/home")
@login_is_required
def home():
    # 检查是否存在刷新令牌
    if 'refresh_token' in session:
        # 使用刷新令牌获取新的访问令牌
        credentials = flow.credentials
        credentials.refresh_token = session['refresh_token']
        credentials.refresh(Request())
        # 现在你可以使用新的访问令牌了

    # ... rest of your code ... 

请记住,安全地存储和管理刷新令牌至关重要,因为它们可以用来访问用户的 Google 账户。

重要提示:

  • 以上代码只是一个简单的示例,并不能直接用于生产环境。
  • 你需要实现自己的机制来安全地存储和检索刷新令牌。
  • 确保你的应用程序符合 Google API 服务条款。

标签:python,flask,google-cloud-platform
From: 78781172

相关文章

  • python 文件(txt)操作
    我有一个txt文件,其中包含一列(大约2000个)单词。每个单词只用一个新行分隔。我想将所有这些单词存储在一个数组中words.txt文件的示例:applebananaorange我尝试过的代码:importrandomwithopen('E:/Code/learn/Projects/word-guessing-game/words.txt','r')a......
  • matplotlib与Python不兼容问题
    “我使用的是Matplotlib版本3.7.2。Anaconda的Spyder中的Python版本是3.8.18。当我运行importmatplotlib.pyplotasplt行时,出现错误module'matplotlib'hasnoattribute'rcParams'。当我尝试将Matplotlib和matplotlib.base包从当前的3.7.2版本升级到3.8......
  • 使用 json 配置文件进行 Python 日志记录
    我玩弄了日志模块,发现了一些我不太清楚的事情。首先,因为这是一个大程序,我希望代码尽可能简单,因此我使用了json配置文件.{"version":1,"disable_existing_loggers":false,"formatters":{"simple":{"format":"%(asctime)s-%(name)s......
  • Python随机库:从帕累托分布进行模拟(使用形状和尺度参数)
    根据Python文档,random.paretovariate(alpha)模拟帕累托分布,其中alpha是形状参数。但帕累托分布同时采用形状和尺度参数。如何从指定这两个参数的分布中进行模拟?你说的对,帕累托分布是由形状和尺度参数定义的,而random.paretovariate(alpha)函数只接受形状参数......
  • 跳过 Python Spark Pyspark Databricks 未知字段异常中的不良记录
    我想知道是否有人知道如何跳过我们从json文件获取的记录这是错误[UNKNOWN_FIELD_EXCEPTION.NEW_FIELDS_IN_RECORD_WITH_FILE_PATH]在解析过程中遇到未知字段:这是失败的代码sent=spark.readStream.format('cloudFiles')\.option('cloudFiles.format','json')......
  • 使用 python 截断 Databricks 中的增量表
    对于Python和SQL,这里给出了Delta表删除操作,并且给出了使用SQL的截断这里但我找不到Python截断表的文档。如何在Databricks中对增量表执行此操作?HowtodoitfordeltatableinDatabricks?虽然Databricks中没有直接使用PythonAPI截断De......
  • 从Python中的列表列表中提取随机列表选择
    我有一个从图像生成的RGB值列表:color_list=[(192,155,120),(132,81,65),(226,226,199),(76,94,117),(140,157,178),(17,34,54),(217,213,139),(134,171,144),(98,123,95),(109,145,96),(181,109,92),(71,47,39),......
  • 这段代码是否保证Python对象被立即删除?
    我正在将Redis异步客户端与Celery一起使用,但在两者之间的集成方面遇到了一些问题。上下文是我需要删除redis.Redis实例(在构造函数中创建)以便关闭连接(该对象有一个close方法,但当asyncio事件循环关闭时我无法使用它,heal_client方法仅在这些情况下才会使用。我的代码如......
  • python selenium 行为错误:AttributeError:“Context”对象没有属性“driver”
    我正在使用pythonselenium与Behavior包一起工作。这是代码:@given('theuserisontheloginpage')defstep_given_user_on_login_page(context):PATH='C:/Users/PycharmProjects/ui_test/chromedriver-win32/chromedriver.exe'context.driver=......
  • python 脚本中的路点用于处理大数据集
    我编写了一个脚本,将一堆来自api的请求写入csv文件。该api中有数千个请求,并且在结束循环/退出程序之前永远不会结束。如何合并航路点,以便如果再次发生停顿,它会自动继续前进并最终打印所有请求?我尝试了一些不起作用的方法,但我不知道下一步该怎么做。以下是使用航路点......