使用Spotify API隐式授权流一段时间后出现401认证错误
问题:Spotify隐式授权令牌过期导致401错误
在Express项目中使用Spotify API的**Implicit Grant(隐式授权)**流交互,一段时间后access_token失效,调用API时控制台返回401错误。原本认为隐式授权的令牌不会过期,尝试获取新令牌也失败。
原因说明
Spotify的隐式授权令牌并非永久有效,当前官方会为这类令牌设置1小时有效期,且隐式授权流程不会返回refresh_token,因此无法通过刷新令牌机制获取新的access_token,令牌过期后必须重新引导用户完成授权。
修复方案
- 记录令牌过期时间:授权成功后,从哈希参数中获取
expires_in(单位秒),计算并存储令牌的过期时间戳 - 使用前校验有效性:每次发起API请求前,检查令牌是否已过期
- 捕获401错误:请求失败返回401时,自动触发重新授权流程
修改后的代码示例
(function () { var stateKey = 'spotify_auth_state'; var tokenKey = 'spotifyAuthToken'; var expiresKey = 'spotifyTokenExpires'; function getHashParams() { var hashParams = {}; var e, r = /([^&;=]+)=?([^&;]*)/g, q = window.location.hash.substring(1); while (e = r.exec(q)) { hashParams[e[1]] = decodeURIComponent(e[2]); } return hashParams; } function generateRandomString(length) { var text = ''; var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (var i = 0; i < length; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }; function isTokenExpired() { var expiresAt = localStorage.getItem(expiresKey); return !expiresAt || Date.now() > parseInt(expiresAt); } var userProfileSource = document.getElementById('user-profile-template').innerHTML, userProfileTemplate = Handlebars.compile(userProfileSource), userProfilePlaceholder = document.getElementById('user-profile'); oauthSource = document.getElementById('oauth-template').innerHTML, oauthTemplate = Handlebars.compile(oauthSource), oauthPlaceholder = document.getElementById('oauth'); var params = getHashParams(); var access_token = params.access_token, state = params.state, expires_in = params.expires_in, storedState = localStorage.getItem(stateKey); // 从本地存储获取令牌(如果哈希中没有) if (!access_token) { access_token = localStorage.getItem(tokenKey); } if (access_token && (state == null || state !== storedState)) { // 状态不匹配,清除无效令牌 localStorage.removeItem(tokenKey); localStorage.removeItem(expiresKey); access_token = null; } else { localStorage.removeItem(stateKey); if (access_token) { // 存储新令牌的过期时间 if (expires_in) { var expiresAt = Date.now() + (parseInt(expires_in) * 1000); localStorage.setItem(expiresKey, expiresAt); } // 校验令牌是否过期 if (isTokenExpired()) { access_token = null; localStorage.removeItem(tokenKey); localStorage.removeItem(expiresKey); } else { $.ajax({ url: 'https://api.spotify.com/v1/me', headers: { 'Authorization': 'Bearer ' + access_token }, success: function (response) { userProfilePlaceholder.innerHTML = userProfileTemplate(response); $('#login').hide(); $('#loggedin').show(); }, error: function(xhr) { if (xhr.status === 401) { // 令牌失效,清除存储并显示登录按钮 localStorage.removeItem(tokenKey); localStorage.removeItem(expiresKey); $('#login').show(); $('#loggedin').hide(); } } }); localStorage.setItem(tokenKey, access_token); } } // 根据令牌状态切换界面 if (!access_token || isTokenExpired()) { $('#login').show(); $('#loggedin').hide(); } document.getElementById('login-button').addEventListener('click', getAccessToken, false); function getAccessToken() { var client_id = '7c35e3a74f364538866db979db4f35f1'; // 你的客户端ID var redirect_uri = 'http://localhost:3000'; // 你的重定向地址 var state = generateRandomString(16); localStorage.setItem(stateKey, state); var scope = 'user-read-private user-read-email user-read-playback-state app-remote-control user-modify-playback-state user-read-currently-playing streaming'; var url = 'https://accounts.spotify.com/authorize'; url += '?response_type=token'; url += '&client_id=' + encodeURIComponent(client_id); url += '&scope=' + encodeURIComponent(scope); url += '&redirect_uri=' + encodeURIComponent(redirect_uri); url += '&state=' + encodeURIComponent(state); window.location.href = url; } } })();
内容的提问来源于stack exchange,提问作者L3N0X
相关产品推荐
相关产品推荐

