首页 > 解决方案 > 在 google api + Django 中使用 State 变量的问题

问题描述

我正在尝试将文件保存到 google 磁盘的功能添加到 django 应用程序。我以烧瓶链接中编写的官方文档中的示例为基础

import google_auth_oauthlib.flow
from googleapiclient.discovery import build
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect

import os
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

SCOPES = 'https://www.googleapis.com/auth/drive'
cred = os.path.join(settings.BASE_DIR, 'credentials.json')

上传文件功能:

def file_to_drive(request, import_file=None):

    state = request.session['state']
    if state is None:
        authorize(request)
    else:
        flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
          cred, scopes=SCOPES, state=state)
        flow.redirect_uri = "http://localhost:8000/oauth2callback"
        authorization_response = request.build_absolute_uri()
        flow.fetch_token(authorization_response=authorization_response)
        credentials = flow.credentials

        service = build('drive', 'v3', credentials=credentials)
        file_metadata = {
            'name': 'My Report',
            'mimeType': 'application/vnd.google-apps.spreadsheet'
        }
        media = MediaFileUpload(import_file,
                                mimetype='text/html',
                                resumable=True)
        file = service.files().create(body=file_metadata,
                                            media_body=media,
                                            fields='id').execute()
        print('File ID: %s' % file.get('id'))
    return (f"https://docs.google.com/document/d/{file.get('id')}/edit")

以及用户授权功能

def authorize(request):

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      cred, scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000/oauth2callback"
    authorization_url, state = flow.authorization_url(
      access_type='offline',
      include_granted_scopes='true')
    request.session['state'] = state
    return HttpResponseRedirect(authorization_url)

网址.py

path('oauth2callback', authorize, name='authorize'),
path('to_drive', file_to_drive, name='file_to_drive'),

在函数file_to_drive中从会话中搜索状态参数的值,如果没有找到,authorize则调用该函数。最后,我收到一条消息

oauthlib.oauth2.rfc6749.errors.MismatchingStateError: (mismatching_state) CSRF Warning! State not equal in request and response.

错误回溯看起来像

  File "/home/y700/projects/CV/cv-base/ems/base/utils.py", line 119, in file_to_drive
    flow.fetch_token(authorization_response=authorization_response)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/google_auth_oauthlib/flow.py", line 263, in fetch_token
    self.client_config['token_uri'], **kwargs)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/requests_oauthlib/oauth2_session.py", line 208, in fetch_token
    state=self._state)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py", line 203, in parse_request_uri_response
    response = parse_authorization_code_response(uri, state=state)
  File "/home/y700/Env/cv-base-XRtgVf2K/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 268, in parse_authorization_code_response
    raise MismatchingStateError()
oauthlib.oauth2.rfc6749.errors.MismatchingStateError: (mismatching_state) CSRF Warning! State not equal in request and response.
[13/Sep/2019 13:19:52] "GET /to_drive HTTP/1.1" 500 98701

错误是由字符串引起的

flow.fetch_token(authorization_response=authorization_response)

标签: pythondjangogoogle-oauthgoogle-api-python-client

解决方案


错误在这行代码中:

authorization_response = request.build_absolute_uri()

它确实适用于烧瓶,但给 Django 带来了麻烦。问题在于 URL 的编码。我尝试了不同的编码器,但没有奏效。所以我使用字符串操作对代码进行了编码。

  #encoding link
  link = request.build_absolute_uri()
  link = str(link)
  s = link.index("code=")
  cut0 = link[0:s+2]
  cut1 = link[s+2:len(link)]
  cut1 = cut1.replace("/","%2F")
  final = cut0+cut1
  authorization_response = final
  flow.fetch_token(authorization_response=authorization_response)

  credentials = flow.credentials
  # Store credentials
  # request.session['credentials'] = credentials_to_dict(credentials)
  # print(request.session['credentials'])

  credentials_dict = credentials_to_dict(credentials)

  # print(credentials_dict)

  credentials = google.oauth2.credentials.Credentials(
    credentials_dict["token"],
    refresh_token = credentials_dict["refresh_token"],
    token_uri = credentials_dict["token_uri"],
    client_id = credentials_dict["client_id"],
    client_secret = credentials_dict["client_secret"],
    scopes = credentials_dict["scopes"])

  service = build('drive', 'v3', credentials=credentials)
    file_metadata = { ......

将凭据存储在 DB 而不是 Session 中。


推荐阅读