如何用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
关键说明
为什么原来的代码无法获取code?
你之前的/login是服务器自己模拟请求Spotify授权页面,相当于服务器在替用户完成授权,但Spotify的OAuth流程要求真实用户(iOS端的用户)完成登录授权,授权完成后才会将code回调到你的指定地址。服务器直接请求的方式跳过了用户交互,自然不会得到有效的code。关于SessionMiddleware
必须用会话存储state,因为state是用来验证请求合法性的,防止恶意第三方伪造回调请求。客户端适配
你的iOS客户端需要引导用户打开浏览器访问你的/login地址,完成授权后,Spotify会跳转回你的/callback,此时你可以将令牌通过Deep Link或页面展示的方式返回给iOS客户端。
内容的提问来源于stack exchange,提问作者Grieze

