从 Django 视图中尝试将 OAuth2 与 Google 工作表一起使用时出现错误 400:redirect_uri_mismatch
Getting a Error 400: redirect_uri_mismatch when trying to use OAuth2 with Google Sheets from a Django view
我正在尝试从 Django 视图连接到 Google 表格的 API。我从这个 link 中获取的大部分代码:
https://developers.google.com/sheets/api/quickstart/python
无论如何,这里是代码:
sheets.py(从上面的link复制粘贴,函数重命名)
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
# The ID and range of a sample spreadsheet.
SAMPLE_SPREADSHEET_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
SAMPLE_RANGE_NAME = 'Class Data!A2:E'
def test():
"""Shows basic usage of the Sheets API.
Prints values from a sample spreadsheet.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('sheets', 'v4', credentials=creds)
# Call the Sheets API
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
range=SAMPLE_RANGE_NAME).execute()
values = result.get('values', [])
if not values:
print('No data found.')
else:
print('Name, Major:')
for row in values:
# Print columns A and E, which correspond to indices 0 and 4.
print('%s, %s' % (row[0], row[4]))
urls.py
urlpatterns = [
path('', views.index, name='index')
]
views.py
from django.http import HttpResponse
from django.shortcuts import render
from .sheets import test
# Views
def index(request):
test()
return HttpResponse('Hello world')
视图函数所做的只是调用 sheets.py 模块中的 test()
方法。无论如何,当我 运行 我的服务器并转到 URL 时,另一个选项卡会打开 Google oAuth2,这意味着已检测到凭据文件和所有内容。但是,在此选项卡中,Google 显示以下错误消息:
Error 400: redirect_uri_mismatch The redirect URI in the request, http://localhost:65262/, does not match the ones authorized for the OAuth client.
在我的 API 控制台中,我将回调 URL 设置为 127.0.0.1:8000
以匹配我的 Django 视图 URL。我什至不知道 http://localhost:65262/
URL 来自哪里。对解决这个问题有帮助吗?有人可以向我解释为什么会这样吗?提前致谢。
编辑
我试图删除评论中提到的流程方法中的 port=0
,然后 URL 与 http://localhost:8080/
不匹配,这又很奇怪,因为我的 Django 应用程序是 运行在 8000
端口中。
重定向 URI 告诉 Google 您希望将授权返回到的位置。这必须在 google 开发人员控制台中正确设置,以避免任何人劫持您的客户端。它必须完全匹配。
到Google developer console。编辑您当前使用的客户端并添加以下内容作为重定向 uri
http://localhost:65262/
提示单击小铅笔图标以编辑客户端 :)
TBH 在开发过程中,只需添加 google 表示您正在调用的端口 fiddle 并在您的应用程序中进行设置即可。
除非您不打算部署代码,否则不应使用 Flow.run_local_server()。这是因为 run_local_server
在服务器上启动浏览器以完成流程。
如果您在本地为自己开发项目,这就很好用了。
如果您打算使用本地服务器协商 OAuth 流程。您的机密中配置的重定向 URI 必须匹配,主机的本地服务器默认值为 localhost
and port is 8080
。
如果您要部署代码,则必须通过用户浏览器、您的服务器和 Google 之间的交换来执行流程。
因为您已经有了一个 Django 服务器 运行,您可以使用它来协商流程。
例如,
假设 Django 项目中有一个带有 urls.py
模块的推文应用程序,如下所示。
from django.urls import path, include
from . import views
urlpatterns = [
path('google_oauth', views.google_oath, name='google_oauth'),
path('hello', views.say_hello, name='hello'),
]
urls = include(urlpatterns)
您可以为需要凭据的视图实施保护,如下所示。
import functools
import json
import urllib
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from django.shortcuts import redirect
from django.http import HttpResponse
SCOPES = ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', 'openid']
def provides_credentials(func):
@functools.wraps(func)
def wraps(request):
# If OAuth redirect response, get credentials
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES,
redirect_uri="http://localhost:8000/tweet/hello")
existing_state = request.GET.get('state', None)
current_path = request.path
if existing_state:
secure_uri = request.build_absolute_uri(
).replace('http', 'https')
location_path = urllib.parse.urlparse(existing_state).path
flow.fetch_token(
authorization_response=secure_uri,
state=existing_state
)
request.session['credentials'] = flow.credentials.to_json()
if location_path == current_path:
return func(request, flow.credentials)
# Head back to location stored in state when
# it is different from the configured redirect uri
return redirect(existing_state)
# Otherwise, retrieve credential from request session.
stored_credentials = request.session.get('credentials', None)
if not stored_credentials:
# It's strongly recommended to encrypt state.
# location is needed in state to remember it.
location = request.build_absolute_uri()
# Commence OAuth dance.
auth_url, _ = flow.authorization_url(state=location)
return redirect(auth_url)
# Hydrate stored credentials.
credentials = Credentials(**json.loads(stored_credentials))
# If credential is expired, refresh it.
if credentials.expired and creds.refresh_token:
creds.refresh(Request())
# Store JSON representation of credentials in session.
request.session['credentials'] = credentials.to_json()
return func(request, credentials=credentials)
return wraps
@provides_credentials
def google_oauth(request, credentials):
return HttpResponse('Google OAUTH <a href="/tweet/hello">Say Hello</a>')
@provides_credentials
def say_hello(request, credentials):
# Use credentials for whatever
return HttpResponse('Hello')
请注意,这只是一个例子。如果您决定走这条路,我建议考虑将 OAuth 流程提取到它自己的 Django 应用程序中。
我遇到了与 redirect_uri 错误相同的问题,事实证明(如上所示)我在 google 控制台中创建了我的凭据类型为“Web 服务器”而不是“桌面”应用程序”。我创建了新的 creds 作为“桌面应用程序”,下载了 JSON 并且它有效。
最终,我想将 GMAIL API 用于 Web 服务器,但这与示例的流程不同。
我正在尝试从 Django 视图连接到 Google 表格的 API。我从这个 link 中获取的大部分代码: https://developers.google.com/sheets/api/quickstart/python
无论如何,这里是代码:
sheets.py(从上面的link复制粘贴,函数重命名)
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
# The ID and range of a sample spreadsheet.
SAMPLE_SPREADSHEET_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
SAMPLE_RANGE_NAME = 'Class Data!A2:E'
def test():
"""Shows basic usage of the Sheets API.
Prints values from a sample spreadsheet.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('sheets', 'v4', credentials=creds)
# Call the Sheets API
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
range=SAMPLE_RANGE_NAME).execute()
values = result.get('values', [])
if not values:
print('No data found.')
else:
print('Name, Major:')
for row in values:
# Print columns A and E, which correspond to indices 0 and 4.
print('%s, %s' % (row[0], row[4]))
urls.py
urlpatterns = [
path('', views.index, name='index')
]
views.py
from django.http import HttpResponse
from django.shortcuts import render
from .sheets import test
# Views
def index(request):
test()
return HttpResponse('Hello world')
视图函数所做的只是调用 sheets.py 模块中的 test()
方法。无论如何,当我 运行 我的服务器并转到 URL 时,另一个选项卡会打开 Google oAuth2,这意味着已检测到凭据文件和所有内容。但是,在此选项卡中,Google 显示以下错误消息:
Error 400: redirect_uri_mismatch The redirect URI in the request, http://localhost:65262/, does not match the ones authorized for the OAuth client.
在我的 API 控制台中,我将回调 URL 设置为 127.0.0.1:8000
以匹配我的 Django 视图 URL。我什至不知道 http://localhost:65262/
URL 来自哪里。对解决这个问题有帮助吗?有人可以向我解释为什么会这样吗?提前致谢。
编辑
我试图删除评论中提到的流程方法中的 port=0
,然后 URL 与 http://localhost:8080/
不匹配,这又很奇怪,因为我的 Django 应用程序是 运行在 8000
端口中。
重定向 URI 告诉 Google 您希望将授权返回到的位置。这必须在 google 开发人员控制台中正确设置,以避免任何人劫持您的客户端。它必须完全匹配。
到Google developer console。编辑您当前使用的客户端并添加以下内容作为重定向 uri
http://localhost:65262/
提示单击小铅笔图标以编辑客户端 :)
TBH 在开发过程中,只需添加 google 表示您正在调用的端口 fiddle 并在您的应用程序中进行设置即可。
除非您不打算部署代码,否则不应使用 Flow.run_local_server()。这是因为 run_local_server
在服务器上启动浏览器以完成流程。
如果您在本地为自己开发项目,这就很好用了。
如果您打算使用本地服务器协商 OAuth 流程。您的机密中配置的重定向 URI 必须匹配,主机的本地服务器默认值为 localhost
and port is 8080
。
如果您要部署代码,则必须通过用户浏览器、您的服务器和 Google 之间的交换来执行流程。
因为您已经有了一个 Django 服务器 运行,您可以使用它来协商流程。
例如,
假设 Django 项目中有一个带有 urls.py
模块的推文应用程序,如下所示。
from django.urls import path, include
from . import views
urlpatterns = [
path('google_oauth', views.google_oath, name='google_oauth'),
path('hello', views.say_hello, name='hello'),
]
urls = include(urlpatterns)
您可以为需要凭据的视图实施保护,如下所示。
import functools
import json
import urllib
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from django.shortcuts import redirect
from django.http import HttpResponse
SCOPES = ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', 'openid']
def provides_credentials(func):
@functools.wraps(func)
def wraps(request):
# If OAuth redirect response, get credentials
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES,
redirect_uri="http://localhost:8000/tweet/hello")
existing_state = request.GET.get('state', None)
current_path = request.path
if existing_state:
secure_uri = request.build_absolute_uri(
).replace('http', 'https')
location_path = urllib.parse.urlparse(existing_state).path
flow.fetch_token(
authorization_response=secure_uri,
state=existing_state
)
request.session['credentials'] = flow.credentials.to_json()
if location_path == current_path:
return func(request, flow.credentials)
# Head back to location stored in state when
# it is different from the configured redirect uri
return redirect(existing_state)
# Otherwise, retrieve credential from request session.
stored_credentials = request.session.get('credentials', None)
if not stored_credentials:
# It's strongly recommended to encrypt state.
# location is needed in state to remember it.
location = request.build_absolute_uri()
# Commence OAuth dance.
auth_url, _ = flow.authorization_url(state=location)
return redirect(auth_url)
# Hydrate stored credentials.
credentials = Credentials(**json.loads(stored_credentials))
# If credential is expired, refresh it.
if credentials.expired and creds.refresh_token:
creds.refresh(Request())
# Store JSON representation of credentials in session.
request.session['credentials'] = credentials.to_json()
return func(request, credentials=credentials)
return wraps
@provides_credentials
def google_oauth(request, credentials):
return HttpResponse('Google OAUTH <a href="/tweet/hello">Say Hello</a>')
@provides_credentials
def say_hello(request, credentials):
# Use credentials for whatever
return HttpResponse('Hello')
请注意,这只是一个例子。如果您决定走这条路,我建议考虑将 OAuth 流程提取到它自己的 Django 应用程序中。
我遇到了与 redirect_uri 错误相同的问题,事实证明(如上所示)我在 google 控制台中创建了我的凭据类型为“Web 服务器”而不是“桌面”应用程序”。我创建了新的 creds 作为“桌面应用程序”,下载了 JSON 并且它有效。
最终,我想将 GMAIL API 用于 Web 服务器,但这与示例的流程不同。