我正在尝试使用 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 获取一个新的访问令牌,这需要用户重新授权。
为了解决这个问题,你需要:
-
请求
offline_access
作用域: 将其添加到你的scopes
列表中:
scopes=["https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"openid",
"offline_access"],
- 存储并使用刷新令牌: 当用户第一次授权你的应用时,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 服务条款。