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

使用Spotify API隐式授权流一段时间后出现401认证错误

问题:Spotify隐式授权令牌过期导致401错误

在Express项目中使用Spotify API的**Implicit Grant(隐式授权)**流交互,一段时间后access_token失效,调用API时控制台返回401错误。原本认为隐式授权的令牌不会过期,尝试获取新令牌也失败。

原因说明

Spotify的隐式授权令牌并非永久有效,当前官方会为这类令牌设置1小时有效期,且隐式授权流程不会返回refresh_token,因此无法通过刷新令牌机制获取新的access_token,令牌过期后必须重新引导用户完成授权。

修复方案

  1. 记录令牌过期时间:授权成功后,从哈希参数中获取expires_in(单位秒),计算并存储令牌的过期时间戳
  2. 使用前校验有效性:每次发起API请求前,检查令牌是否已过期
  3. 捕获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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:31:02