Heroku部署Gmail API服务端应用:无需input()获取OAuth2授权码
解决Heroku上Google OAuth2授权无需
input()获取授权码的问题 你当前的代码是桌面应用的OAuth授权流程,依赖交互式终端输入授权码,但Heroku是无头(无终端交互)的服务端环境,所以input()会触发EOFError。服务端Web应用需要改用基于回调的OAuth2授权流程,以下是具体解决方案:
核心思路调整
- 把Google OAuth客户端类型从「桌面应用」改成「Web应用」,配置自己的回调地址(不能用
developers.google.com/oauthplayground)。 - 用Web路由处理授权请求和回调,自动获取授权码,无需手动输入。
- 避免用本地文件保存凭证(Heroku临时文件系统重启会丢失),改用环境变量或数据库持久化。
完整代码示例(基于Flask,Heroku常用Web框架)
from flask import Flask, redirect, request, session import google_auth_oauthlib.flow import pickle import os import base64 import json from googleapiclient.discovery import build from google.auth.transport.requests import Request # 初始化Flask应用 app = Flask(__name__) app.secret_key = os.environ.get('FLASK_SECRET_KEY') # 从Heroku环境变量设置随机密钥 # 配置参数 SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'] # 替换为你的权限范围 REDIRECT_URI = 'https://your-heroku-app.herokuapp.com/oauth2callback' # 替换为你的Heroku应用回调地址 CLIENT_SECRETS = json.loads(os.environ.get('GOOGLE_APPLICATION_CREDENTIALS')) def load_or_refresh_credentials(): creds = None # 从环境变量加载已保存的凭证(Base64编码避免格式问题) saved_creds = os.environ.get('SAVED_GOOGLE_CREDS') if saved_creds: creds = pickle.loads(base64.b64decode(saved_creds)) # 刷新过期凭证或重新授权 if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) # 刷新后重新保存凭证 updated_creds = base64.b64encode(pickle.dumps(creds)).decode('utf-8') # 提示:Heroku环境变量需通过CLI或控制面板持久化,这里仅内存修改示例 os.environ['SAVED_GOOGLE_CREDS'] = updated_creds return creds # 授权入口路由 @app.route('/authorize') def start_authorization(): flow = google_auth_oauthlib.flow.Flow.from_client_config( CLIENT_SECRETS, scopes=SCOPES) flow.redirect_uri = REDIRECT_URI # 生成授权URL和state(用于CSRF验证) authorization_url, state = flow.authorization_url( access_type='offline', # 申请离线访问权限,获取刷新令牌 include_granted_scopes='true') session['state'] = state return redirect(authorization_url) # OAuth回调路由,自动获取授权码 @app.route('/oauth2callback') def handle_oauth_callback(): # 验证state防止跨站攻击 session_state = session.get('state') if request.args.get('state') != session_state: return '无效的state参数', 400 # 初始化Flow并传入state flow = google_auth_oauthlib.flow.Flow.from_client_config( CLIENT_SECRETS, scopes=SCOPES, state=session_state) flow.redirect_uri = REDIRECT_URI # 用请求中的code获取令牌(替代原来的input手动输入) flow.fetch_token(code=request.args.get('code')) creds = flow.credentials # 序列化凭证并保存到环境变量 serialized_creds = base64.b64encode(pickle.dumps(creds)).decode('utf-8') os.environ['SAVED_GOOGLE_CREDS'] = serialized_creds return '授权成功!请关闭此页面。' # 业务逻辑示例:调用Gmail API @app.route('/get-gmail') def fetch_gmail_data(): creds = load_or_refresh_credentials() if not creds: # 无凭证时跳转授权 return redirect('/authorize') service = build('gmail', 'v1', credentials=creds) results = service.users().messages().list(userId='me').execute() messages_count = len(results.get('messages', [])) return f'已获取到{messages_count}封邮件' if __name__ == '__main__': # 适配Heroku端口 app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
关键说明
flow.fetch_token的正确用法:
在Web流程中,不需要传入authorization_response(那是桌面应用的完整回调URL),而是直接传code=request.args.get('code'),这个code是Google重定向到回调地址时自动携带的参数。Heroku部署注意事项:
- 在Google Cloud控制台创建Web应用类型的OAuth客户端ID,添加你的
REDIRECT_URI到授权回调地址列表。 - 在Heroku控制面板设置环境变量:
GOOGLE_APPLICATION_CREDENTIALS:Google客户端JSON的字符串版本。FLASK_SECRET_KEY:随机生成的字符串(用于Flask session加密)。- 首次授权后,
SAVED_GOOGLE_CREDS会被生成,可通过heroku config:set SAVED_GOOGLE_CREDS=xxx持久化。
- 在Google Cloud控制台创建Web应用类型的OAuth客户端ID,添加你的
凭证持久化:
Heroku的本地文件系统是临时的,所以不能用token.pickle保存凭证,改用Base64编码后存在环境变量,或用数据库(如Heroku Postgres)存储更可靠。
内容的提问来源于stack exchange,提问作者Shivam Singh
相关产品推荐
相关产品推荐

