从 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 服务器,但这与示例的流程不同。