使用PKCE授权码调用Spotify API时提示code verifier无效
问题排查与修正:Spotify PKCE授权中Code Verifier无效的问题
核心问题分析
- 随机字符串生成错误:
Generate_Random_String函数中,str(values)会把字符列表转为带方括号、引号和逗号的格式(比如['a','b','c']),完全不符合PKCE对code verifier的要求——它必须是指定字符集组成的纯字符串,长度43-128位。 - 哈希处理逻辑错误:PKCE的S256方法要求对code verifier的原始字节做SHA256哈希,再对哈希的二进制结果做base64 URL安全编码。你的代码错误地将哈希的十六进制字符串作为输入编码,导致code challenge与verifier完全不匹配。
修正后的代码
from dotenv import load_dotenv import os import base64 import requests import random import hashlib from urllib.parse import urlparse, urlunparse, urlencode load_dotenv() client_ID = os.getenv("CLIENT_ID") client_secret = os.getenv("CLIENT_SECRET") def Generate_Random_String(length): possible_Values = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" # 直接生成随机字符拼接成纯字符串 return ''.join(random.choice(possible_Values) for _ in range(length)) code_Verifier = Generate_Random_String(64) # 用with语句安全处理文件写入,自动关闭文件 with open("Code Verifier.txt","w") as perm_code_verifier: perm_code_verifier.write(code_Verifier) def code_hash(plain): # 返回SHA256哈希的二进制结果,而非十六进制字符串 return hashlib.sha256(plain.encode("utf-8")).digest() def base64encode(hashed_bytes): # 对二进制哈希结果做URL安全的base64编码 auth_base64 = base64.urlsafe_b64encode(hashed_bytes).decode("utf-8") # 移除末尾的等号 return auth_base64.rstrip("=") hashed = code_hash(code_Verifier) codeChallenge = base64encode(hashed) scope = "user-read-private user-read-email" authURL = urlparse("https://accounts.spotify.com/authorize") redirectURI = "http://localhost:3000" parameters = { "response_type": "code", "client_id": client_ID, "scope": scope, "code_challenge_method": "S256", "code_challenge": codeChallenge, "redirect_uri" : redirectURI, } authUrl_incl_params = authURL._replace(query=urlencode(parameters)) final_url = urlunparse(authUrl_incl_params) print("Go to ", final_url, "to authenticate") code = input("Enter code recieved:") def get_token(code): with open("Code Verifier.txt") as file: # 读取时去除可能的换行符,避免空白字符干扰 codeVerifier = file.read().strip() url = "https://accounts.spotify.com/api/token" payload = { "client_id": client_ID, "grant_type": "authorization_code", "code": code, "redirect_uri": redirectURI, "code_verifier": codeVerifier, } headers = { "Content-Type": "application/x-www-form-urlencoded", } try: response = requests.post(url, data=payload, headers=headers) response.raise_for_status() # 主动抛出HTTP错误,便于调试 response_ans = response.json() print(response_ans) token = response_ans.get("access_token") return token except Exception as e: print(f"Error: {e}") print(f"Response content: {response.text}") # 打印响应内容,辅助排查其他问题 token = get_token(code) print(token)
额外优化说明
- 用
with语句处理文件操作,避免资源泄漏。 - 添加
response.raise_for_status()和响应内容打印,方便排查redirect_uri不匹配、code过期等其他潜在问题。 - 读取code verifier时用
strip()去除可能的换行符,防止写入文件时的意外空白导致验证失败。
内容的提问来源于stack exchange,提问作者willow
相关产品推荐
相关产品推荐

