使用Spotify Web Playback SDK遇401,如何通过授权码流刷新令牌?
解决Spotify授权码流的令牌刷新问题
核心逻辑
- 令牌刷新必须在后端完成,因为需要使用
SPOTIFY_CLIENT_SECRET,该密钥绝对不能暴露在前端代码中 - 前端需要跟踪access_token的过期时间,提前触发刷新请求,避免令牌过期导致播放中断
- 后端不能用全局变量存储用户令牌,必须用会话(如Express Session)或数据库安全存储,防止多用户场景下令牌冲突
步骤1:修复后端令牌存储问题
你的现有代码用全局变量存储access_token和refresh_token,这在多用户场景下会导致令牌覆盖,必须改为会话存储。以Express的express-session为例:
1.1 安装并配置Session
npm install express-session
在Express应用中添加Session配置:
const session = require('express-session'); app.use(session({ secret: 'your-custom-secret-key', // 替换为自己的安全密钥 resave: false, saveUninitialized: true, cookie: { secure: false } // 开发环境用false,生产环境需开启HTTPS并设为true }));
1.2 修改认证回调与令牌接口
将令牌存储到用户会话中,而非全局变量:
router.get("/auth/callback", (req, res) => { console.log("spotify callback"); var code = req.query.code; var authOptions = { url: "https://accounts.spotify.com/api/token", form: { code: code, redirect_uri: "http://localhost:3001/spotify/auth/callback", grant_type: "authorization_code", }, headers: { Authorization: "Basic " + Buffer.from( process.env.SPOTIFY_CLIENT_ID + ":" + process.env.SPOTIFY_CLIENT_SECRET ).toString("base64"), "Content-Type": "application/x-www-form-urlencoded", }, json: true, }; request.post(authOptions, function (error, response, body) { if (!error && response.statusCode === 200) { console.log("auth ok", body); // 将令牌存入用户会话 req.session.access_token = body.access_token; req.session.refresh_token = body.refresh_token; req.session.expires_in = body.expires_in; // 记录过期时长(秒) res.redirect("http://localhost:8080"); } else { console.log("ERROR AUTH", error, response); } }); }); router.get("/auth/token", (req, res) => { res.json({ access_token: req.session.access_token, refresh_token: req.session.refresh_token, expires_in: req.session.expires_in // 返回过期时长给前端 }); });
步骤2:新增后端刷新令牌接口
创建专用接口,使用refresh_token向Spotify请求新的access_token:
router.post("/auth/refresh", (req, res) => { const refresh_token = req.session.refresh_token; if (!refresh_token) { return res.status(401).json({ error: "No refresh token available" }); } const authOptions = { url: "https://accounts.spotify.com/api/token", form: { grant_type: "refresh_token", refresh_token: refresh_token }, headers: { Authorization: "Basic " + Buffer.from( process.env.SPOTIFY_CLIENT_ID + ":" + process.env.SPOTIFY_CLIENT_SECRET ).toString("base64"), "Content-Type": "application/x-www-form-urlencoded", }, json: true, }; request.post(authOptions, function (error, response, body) { if (!error && response.statusCode === 200) { // 更新会话中的令牌,部分场景下Spotify会返回新的refresh_token req.session.access_token = body.access_token; if (body.refresh_token) { req.session.refresh_token = body.refresh_token; } req.session.expires_in = body.expires_in; res.json({ access_token: body.access_token, expires_in: body.expires_in, refresh_token: body.refresh_token || req.session.refresh_token }); } else { console.log("ERROR REFRESH", error, response); res.status(response.statusCode).json(body); } }); });
步骤3:前端处理令牌刷新逻辑
在React应用中实现自动刷新逻辑,提前触发刷新请求,避免播放中断:
import { useEffect, useState } from 'react'; function SpotifyPlayer() { const [tokenData, setTokenData] = useState(null); const [refreshTimer, setRefreshTimer] = useState(null); // 初始化获取令牌 useEffect(() => { fetch("http://localhost:3001/spotify/auth/token") .then(res => res.json()) .then(data => { setTokenData(data); setupRefreshTimer(data.expires_in); }); }, []); // 设置刷新定时器(提前30秒触发,预留网络请求时间) const setupRefreshTimer = (expiresIn) => { if (refreshTimer) clearTimeout(refreshTimer); const refreshTime = (expiresIn - 30) * 1000; const timer = setTimeout(() => { refreshToken(); }, refreshTime); setRefreshTimer(timer); }; // 请求刷新令牌并更新播放器 const refreshToken = () => { fetch("http://localhost:3001/spotify/auth/refresh", { method: "POST" }) .then(res => res.json()) .then(data => { if (!data.error) { setTokenData(data); setupRefreshTimer(data.expires_in); // 更新Spotify播放器的access_token window.Spotify.Player.setAccessToken(data.access_token); } else { // 刷新失败,引导用户重新登录 window.location.href = "http://localhost:3001/spotify/auth"; // 替换为你的授权入口地址 } }); }; // 组件卸载时清理定时器 useEffect(() => { return () => { if (refreshTimer) clearTimeout(refreshTimer); }; }, [refreshTimer]); // 渲染播放器组件逻辑 return ( <div> {/* 此处添加Spotify Web Playback SDK的播放器渲染代码 */} </div> ); } export default SpotifyPlayer;
关键注意事项
- 绝对禁止在前端暴露
SPOTIFY_CLIENT_SECRET:所有与Spotify令牌相关的请求必须通过后端代理 - 处理刷新失败场景:如果refresh_token过期或无效,需引导用户重新完成授权流程
- 生产环境安全配置:使用HTTPS协议,开启Session的secure cookie,避免令牌被窃取
内容的提问来源于stack exchange,提问作者slash89mf
相关产品推荐
相关产品推荐

