Flask Session跨请求丢失数据:Spotify令牌无法留存求助
问题现象
使用Flask搭建后端实现Spotify登录获取令牌,进而自动创建播放列表时,/callback函数中已将令牌数据存入session(打印显示数据符合预期),但调用/generate-playlist接口时,session中的令牌数据为空,导致播放列表创建失败。/generate-playlist的打印结果如下:
session in generate_playlist: <FileSystemSession {'_permanent': True}> token_info: no session was set
已排除的情况:前端无CORS错误,Spotify令牌本身有效,GPT-4调用正常,测试数据也无法跨请求留存,确定问题仅出在Flask Session的配置或使用上。
代码示例(app.py)
from flask import Flask, request, redirect, session, jsonify, render_template from flask_session import Session from flask_cors import CORS from dotenv import load_dotenv import spotipy from spotipy.oauth2 import SpotifyOAuth, SpotifyOauthError from openai import OpenAI import json import os print('app.py running') load_dotenv() app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) app.config['SESSION_TYPE'] = 'filesystem' app.config.from_object(__name__) Session(app) CORS(app, resources={r"/*": {"origins": "http://localhost:3000", "allow_headers": "*", "methods": ["GET", "POST", "OPTIONS"]}}, supports_credentials=True) SPOTIFY_CLIENT_ID = os.getenv('SPOTIFY_CLIENT_ID') SPOTIFY_CLIENT_SECRET = os.getenv('SPOTIFY_CLIENT_SECRET') SPOTIFY_REDIRECT_URI = os.getenv('SPOTIFY_REDIRECT_URI') OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') # Set up Spotify authentication oauth = SpotifyOAuth( client_id=SPOTIFY_CLIENT_ID, client_secret=SPOTIFY_CLIENT_SECRET, redirect_uri=SPOTIFY_REDIRECT_URI, scope='user-read-private user-read-email user-read-playback-state user-modify-playback-state user-read-recently-played user-top-read playlist-modify-public' ) client = OpenAI( api_key=os.environ['OPENAI_API_KEY'], # this is also the default, it can be omitted ) @app.route('/') def home(): return 'hello' @app.route('/login') def login(): auth_url = oauth.get_authorize_url() return redirect(auth_url) @app.route('/callback') def callback(): session.clear() code = request.args.get('code') if code is None: return redirect('http://localhost:3000?error=missing_code') try: token_info = oauth.get_access_token(code) Session["token_info"] = token_info print('cb session: ', Session) return redirect('http://localhost:3000') except SpotifyOauthError as e: print('spotify oauth error') return redirect(f'http://localhost:3000?error={str(e)}') @app.route('/generate-playlist', methods=['POST', 'OPTIONS']) def generate_playlist(): print('session in generate_playlist early: ', session) if request.method == 'OPTIONS': return '', 204 prompt = request.json['prompt'] # Use the OpenAI API to generate a playlist based on the prompt print('creating openai') openai_response = client.chat.completions.create( model='gpt-4', messages=[ {"role": "user", "content": f'Generate a playlist based on the following prompt and format into a JSON array of song name, song artist, and whether or not it is on spotify, example being [["One More Time", "Daft Punk", "Yes"], ["Aerodynamic", "Daft Punk", "Yes"]]: {prompt}'} ] ) playlist_array = openai_response.choices[0].message.content.strip() print('gen text: ', playlist_array) playlist_data = json.loads(playlist_array) print('pl: ', playlist_data) print('session in generate_playlist: ', session) # Add this line to check the session token_info = session.get("token_info", "no session was set") print('token_info:', token_info) if token_info is None: print('Missing token') return jsonify({'error': 'missing_token'}), 401 try: sp = spotipy.Spotify(auth=token_info['access_token']) track_ids = [] for track_info in playlist_data: track_name, artist_name, on_spotify = track_info if on_spotify.lower() == 'yes': results = sp.search(q=f'track:{track_name} artist:{artist_name}', type='track', limit=1) if results['tracks']['items']: track_id = results['tracks']['items'][0]['id'] track_ids.append(track_id) # Create a new playlist on Spotify print('track ids: ', track_ids) user_id = sp.current_user()['id'] playlist = sp.user_playlist_create(user_id, name=prompt, public=True) # Add the tracks to the playlist if track_ids: print('adding track ids') sp.user_playlist_add_tracks(user_id, playlist['id'], track_ids) playlist_response = { 'name': playlist['name'], 'url': playlist['external_urls']['spotify'], 'tracks': [{'name': track_info[0], 'artist': track_info[1]} for track_info in playlist_data], } print('returning playlist data') return jsonify(playlist_response) except spotipy.SpotifyException as e: print('Spotify API error:', e) return jsonify({'error': 'spotify_api_error', 'message': str(e)}), 500 except Exception as e: print('Error:', e) return jsonify({'error': 'internal_server_error', 'message': str(e)}), 500 if __name__ == "__main__": app.run(debug=True)
问题原因及解决办法
1. 核心错误:误用Session类而非session请求上下文对象
在/callback函数中,错误地使用Session["token_info"] = token_info存储数据,但Session是flask_session提供的类,并非当前请求的session实例。正确做法是使用从flask导入的session对象:
# 错误写法 Session["token_info"] = token_info # 正确写法 session["token_info"] = token_info
同时,打印session的代码也需修正:
# 错误写法 print('cb session: ', Session) # 正确写法 print('cb session: ', session)
2. 确保session持久化(可选)
若需要session在浏览器关闭后仍保留,可在存储token后设置session为持久化:
session["token_info"] = token_info session.permanent = True # 添加该行
3. 前端请求需携带Credentials
虽然已在CORS配置中设置supports_credentials=True,但前端发送请求时必须明确携带credentials,否则session标识(Cookie)不会被传递:
- Fetch API示例:
fetch('http://localhost:5000/generate-playlist', { method: 'POST', credentials: 'include', // 必须添加 headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: '你的提示词' }) }) - Axios示例:
axios.post('http://localhost:5000/generate-playlist', { prompt: '你的提示词' }, { withCredentials: true // 必须添加 })
4. 验证session配置正确性
当前已设置SESSION_TYPE='filesystem',可补充以下配置明确session存储规则,避免潜在问题:
app.config['SESSION_FILE_DIR'] = './flask_session' # 指定session文件存储目录 app.config['SESSION_FILE_THRESHOLD'] = 500 # 最大存储session数量 app.config['SESSION_PERMANENT'] = False # 默认不持久化,按需调整
总结
最关键的错误是在/callback中误用Session类而非session请求上下文对象存储令牌,修正后即可解决session跨请求丢失的问题。同时需确保前端请求携带credentials,保证session标识能正确传递。
内容的提问来源于stack exchange,提问作者Nisus Imke

