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

如何用FastAPI获取Spotify Web API用户访问令牌?回调参数为空问题

问题分析与解决方案

你的核心问题出在/login路由的实现逻辑上——你错误地让服务器通过httpx主动请求Spotify授权页面,而不是引导用户(iOS客户端的用户)的浏览器/客户端直接跳转到Spotify的授权地址。这导致Spotify无法将授权码code和state回调到你的/callback端点。

步骤1:修复/login路由

正确的做法是直接生成Spotify的授权URL,然后让用户的客户端重定向到这个URL,由用户完成授权操作后,Spotify会自动将code和state带回到你的REDIRECT_URI(也就是你的/callback)。

同时,为了防止CSRF攻击,需要将生成的state存储到用户的会话中,后续在/callback里验证。

首先需要为FastAPI添加会话支持,使用SessionMiddleware:

from fastapi import FastAPI, Request, Query, RedirectResponse
from fastapi.middleware.session import SessionMiddleware
import urllib.parse
import secrets

# 初始化app并添加会话中间件
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=secrets.token_hex(16))  # 替换为你的密钥

# 配置参数
CLIENT_ID = "你的Spotify客户端ID"
REDIRECT_URI = "你的回调地址(比如http://localhost:8000/callback)"
SPOTIFY_AUTH_URI = "https://accounts.spotify.com/authorize"
SPOTIFY_TOKEN_URI = "https://accounts.spotify.com/api/token"

然后重写/login路由:

@app.get("/login")
async def login(request: Request):
    # 生成随机state
    state = secrets.token_urlsafe(16)
    # 将state存入用户会话
    request.session["state"] = state
    
    # 构造授权参数
    params = {
        "client_id": CLIENT_ID,
        "response_type": "code",
        "redirect_uri": REDIRECT_URI,
        "state": state,
        # 可以添加需要的scope,比如播放音乐需要的权限
        "scope": "user-read-private user-read-email streaming"
    }
    
    # 构造授权URL
    auth_url = f"{SPOTIFY_AUTH_URI}?{urllib.parse.urlencode(params)}"
    # 重定向到Spotify授权页面
    return RedirectResponse(url=auth_url)

步骤2:修复/callback路由

现在Spotify会将code和state作为查询参数传递到/callback,你需要先验证state与会话中存储的一致,然后向Spotify的/api/token端点POST请求获取访问令牌。

import httpx
from typing import Union

@app.get("/callback")
async def callback(
    request: Request,
    code: Union[str, None] = Query(default=None),
    state: Union[str, None] = Query(default=None),
    error: Union[str, None] = Query(default=None)
):
    # 处理授权错误
    if error:
        return {"error": error, "message": "授权失败"}
    
    # 验证state,防止CSRF
    stored_state = request.session.get("state")
    if not state or state != stored_state:
        return {"error": "invalid_state", "message": "状态验证失败"}
    
    # 向Spotify请求访问令牌
    async with httpx.AsyncClient() as client:
        response = await client.post(
            SPOTIFY_TOKEN_URI,
            data={
                "grant_type": "authorization_code",
                "code": code,
                "redirect_uri": REDIRECT_URI,
                "client_id": CLIENT_ID,
                "client_secret": "你的Spotify客户端密钥"  # 生产环境建议用环境变量存储,不要硬编码
            }
        )
        response_data = response.json()
    
    # 返回令牌数据(或根据业务逻辑处理后返回给iOS客户端)
    return response_data

关键说明

  1. 为什么原来的代码无法获取code?
    你之前的/login是服务器自己模拟请求Spotify授权页面,相当于服务器在替用户完成授权,但Spotify的OAuth流程要求真实用户(iOS端的用户)完成登录授权,授权完成后才会将code回调到你的指定地址。服务器直接请求的方式跳过了用户交互,自然不会得到有效的code。

  2. 关于SessionMiddleware
    必须用会话存储state,因为state是用来验证请求合法性的,防止恶意第三方伪造回调请求。

  3. 客户端适配
    你的iOS客户端需要引导用户打开浏览器访问你的/login地址,完成授权后,Spotify会跳转回你的/callback,此时你可以将令牌通过Deep Link或页面展示的方式返回给iOS客户端。

内容的提问来源于stack exchange,提问作者Grieze

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 11:06:47