You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 11:32:02