Django集成Google Drive API部署至生产环境后出现(mismatching_state) CSRF状态不匹配错误求助
解决Django生产环境Google Drive上传的CSRF State不匹配问题
这个问题我之前也遇到过,根源在于你用错了OAuth2的授权流程——InstalledAppFlow.run_local_server()是给本地桌面应用设计的,它会启动一个本地临时服务器处理授权回调,完全不适合生产环境的Web应用场景,这才导致了CSRF state不匹配的错误。下面是具体的排查和解决步骤:
核心问题分析
InstalledAppFlow的本地授权流程会在你的机器上生成一个state值并启动本地服务等待回调,但生产环境中,用户的请求是发送到你的Web服务器,而Google的回调无法正确关联到这个本地生成的state,自然就出现了“state not equal in request and response”的错误。我们需要切换到Web应用专用的OAuth2授权流程。
解决方案步骤
1. 调整Google Cloud Console的OAuth2配置
首先登录Google Cloud Console,找到你的项目的OAuth2客户端ID设置:
- 在“授权重定向URI”中添加你生产环境的回调URL,比如
https://your-production-domain.com/drive/auth/callback(后续代码会用到这个地址)。
2. 新增Django视图处理授权请求与回调
我们需要两个视图:一个发起授权请求,一个处理Google的回调并保存凭据。
发起授权的视图
from django.shortcuts import redirect from django.http import HttpResponse from google_auth_oauthlib.flow import Flow import os import pickle def drive_auth(request): # 建议用环境变量存储密钥文件路径,不要硬编码 CLIENT_SECRET_FILE = os.getenv( "GOOGLE_CLIENT_SECRET_PATH", "/home/djuka/reusabletechnologies/project_app/reBankMini/reBankMiniApp/client_secret_156600557463-8e2qka5c4t646t7t4ksmbluo3aovv4q6.apps.googleusercontent.com.json" ) SCOPES = ['https://www.googleapis.com/auth/drive'] REDIRECT_URI = "https://your-production-domain.com/drive/auth/callback" # 替换成你的生产回调地址 flow = Flow.from_client_secrets_file( CLIENT_SECRET_FILE, scopes=SCOPES, redirect_uri=REDIRECT_URI ) # 生成授权URL和state值,state用于CSRF验证 authorization_url, state = flow.authorization_url( access_type='offline', # 请求离线访问权限,以便刷新令牌 include_granted_scopes='true' ) # 将state存入session,回调时验证 request.session['drive_auth_state'] = state return redirect(authorization_url)
处理授权回调的视图
def drive_auth_callback(request): CLIENT_SECRET_FILE = os.getenv( "GOOGLE_CLIENT_SECRET_PATH", "/home/djuka/reusabletechnologies/project_app/reBankMini/reBankMiniApp/client_secret_156600557463-8e2qka5c4t646t7t4ksmbluo3aovv4q6.apps.googleusercontent.com.json" ) SCOPES = ['https://www.googleapis.com/auth/drive'] REDIRECT_URI = "https://your-production-domain.com/drive/auth/callback" # 验证CSRF state session_state = request.session.get('drive_auth_state') request_state = request.GET.get('state') if not session_state or session_state != request_state: return HttpResponse('CSRF state mismatch', status=400) # 用state和回调code获取凭据 flow = Flow.from_client_secrets_file( CLIENT_SECRET_FILE, scopes=SCOPES, state=session_state, redirect_uri=REDIRECT_URI ) flow.fetch_token(code=request.GET.get('code')) # 保存凭据到pickle文件(生产环境建议改用数据库存储,适配多实例场景) cred = flow.credentials pickle_file = "/path/to/production-safe/storage/token_drive_v3.pickle" # 确保路径有读写权限 with open(pickle_file, 'wb') as token: pickle.dump(cred, token) return HttpResponse('Drive授权成功!可以开始上传文件了。')
3. 修改Create_Service函数适配生产环境
移除本地授权逻辑,只保留凭据加载和刷新功能:
import pickle import os from googleapiclient.discovery import build from google.auth.transport.requests import Request def Create_Service(client_secret_file, api_name, api_version, scopes): API_SERVICE_NAME = api_name API_VERSION = api_version SCOPES = scopes cred = None pickle_file = "/path/to/production-safe/storage/token_drive_v3.pickle" # 和回调视图中的路径一致 # 加载已保存的凭据 if os.path.exists(pickle_file): with open(pickle_file, 'rb') as token: cred = pickle.load(token) # 刷新过期的凭据 if cred and cred.expired and cred.refresh_token: cred.refresh(Request()) with open(pickle_file, 'wb') as token: pickle.dump(cred, token) try: service = build(API_SERVICE_NAME, API_VERSION, credentials=cred) print(f"{API_SERVICE_NAME} service created successfully") return service except Exception as e: print('Unable to connect.') print(e) return None
4. 调整send_drive函数(仅需少量修改)
def send_drive(file_name, file_path): CLIENT_SECRET_FILE = os.getenv( "GOOGLE_CLIENT_SECRET_PATH", "/home/djuka/reusabletechnologies/project_app/reBankMini/reBankMiniApp/client_secret_156600557463-8e2qka5c4t646t7t4ksmbluo3aovv4q6.apps.googleusercontent.com.json" ) API_NAME = 'drive' API_VERSION = 'v3' SCOPES = ['https://www.googleapis.com/auth/drive'] service = Create_Service(CLIENT_SECRET_FILE, API_NAME, API_VERSION, SCOPES) if not service: return None # 处理服务创建失败的情况 # 上传文件逻辑不变 file_metadata = { 'name': file_name, 'parents': ['1WpY7cw3S5RAPvCfFyPMDkw0I3vIZfQ_c'] } media_content = MediaFileUpload(file_path, mimetype='application/pdf') file = service.files().create( body=file_metadata, media_body=media_content ).execute() print(file) return file
5. 生产环境初始化
部署完成后,先访问/drive/auth(对应你配置的URL)完成一次授权流程,生成token_drive_v3.pickle文件,之后send_drive函数就能正常工作了。
内容的提问来源于stack exchange,提问作者ThuggyTM
相关产品推荐
相关产品推荐

