从 JavaScript 弹出窗口获取访问令牌 URL [Spotify 鉴权]

7
我正在尝试使用纯JavaScript创建Spotify授权流程,以便用户可以登录,然后我可以为他们的帐户添加新播放列表。根据我阅读的说明,我使用一个授权弹出窗口,一旦用户登录,就会在URL中拥有访问令牌。我现在有一个弹出窗口,用户可以进行身份验证,一旦他们这样做,它将在URL中拥有访问令牌。
我需要从我的弹出窗口获取URL并将其保存为全局变量,但我在JavaScript中遇到了困难。

https://codepen.io/martin-barker/pen/YzPwXaz

我的 codepen 打开了一个带有 let popup = window.open( 的弹出窗口,我可以在弹出窗口中运行一个函数来检测用户何时成功验证并且 URL 发生更改吗?如果是这种情况,我想保存 URL 以进行解析并关闭我的弹出窗口。
我的 JavaScript 代码如下:
async function spotifyAuth() {
let result = spotifyLogin()
}

//open popup
function spotifyLogin() {
console.log("inside spotifyLogin, opening popup")

let popup = window.open(`https://accounts.spotify.com/authorize?client_id=5a576333cfb1417fbffbfa3931b00478&response_type=token&redirect_uri=https://codepen.io/martin-barker/pen/YzPwXaz&show_dialog=true&scope=playlist-modify-public`, 'Login with Spotify', 'width=800,height=600')

}

//get url from popup and parse access token????
window.spotifyCallback = (payload) => {
console.log("inside window? ") //this line never appears in console
popup.close()
fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${payload}`
}
}).then(response => {
return response.json()
}).then(data => {
// do something with data
})
}
2个回答

9
这里是我用JavaScript实现的方法。像你提到的那样,使用全局变量: var access_token = null; 我的URL看起来像这样: https://...home.jsp#access_token=BQAXe5JQOV_xZmAukmw6G430lreF......rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s&token_type=Bearer&expires_in=3600&state=vURQeVAoZqwYm4dC 在Spotify将用户重定向到您在仪表板上指定的URI后,我解析带有访问令牌的哈希URL,如下所示:
var hash = window.location.hash.substring(1);
var accessString = hash.indexOf("&");

/* 13 because that bypasses 'access_token' string */
access_token = hash.substring(13, accessString);
console.log("Access Token: " + access_token);

输出结果如下:

访问令牌:BQAXe5JQOV_xZmAukmw6G430lreF...........rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s

我在sessionStorage中保存此访问令牌,以防用户离开页面且url不包含access_token。我假设这是隐式授权流程,因为您想要使用纯JavaScript。只需确保每小时重新获取一次access token,因为它们会过期。

补充说明

我可以向您展示如何在示例中获取令牌并使用它。

我在.html页面上有一个按钮,点击该按钮会调用名为implicitGrantFlow()的函数,该函数位于名为Test.js的JavaScript文件中。

function implicitGrantFlow() {

/* If access token has been assigned in the past and is not expired, no request required. */
if (sessionStorage.getItem("accessToken") !== null &&
    sessionStorage.getItem("tokenTimeStamp") !== null &&
    upTokenTime < tokenExpireSec) {
        var timeLeft = (tokenExpireSec - upTokenTime);
        console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");

        /* Navigate to the home page. */
        $(location).attr('href', "home.jsp");
} else {
    console.log("Token expired or never found, getting new token.");
    $.ajax({
        url: auth_url,
        type: 'GET',
        contentType: 'application/json',
        data: {
            client_id: client_id,
            redirect_uri: redirect_uri,
            scope: scopes,
            response_type: response_type_token,
            state: state
        }
    }).done(function callback(response) {
        /* Redirect user to home page */
        console.log("COULD THIS BE A SUCCESS?");
        $(location).attr('href', this.url);

    }).fail(function (error) {
        /* Since we cannot modify the server, we will always fail. */
        console.log("ERROR HAPPENED: " + error.status);
        console.log(this.url);
        $(location).attr('href', this.url);
    });
}

我正在做的是检查我在sessionStorage中存储的access_token信息是否为空。我使用时间戳生成令牌创建时间和理论上到期时间。如果这些参数满足条件,那么我不会再进行另一个调用。
否则,我将调用以获取访问令牌,如果成功,将重定向到我在先前写的URI(你会看到我在.fail部分中设置了重定向。这是因为我在学校服务器上没有权限设置绕过CORS相关问题的设置,即使我创建的重定向url没问题.)。
然后当我的白名单URI加载(重定向到我的主页)时,我利用我的标签。
home.jsp
<body onload="getAccessToken()">

在我的<tag>标签中,我有一个调用函数的方法,使页面加载一次。这会调用函数getAccessTokens()。

/**
 * The bread and butter to calling the API. This function will be called once the
 * user is redirected to the home page on success and without rejecting the terms
 * we are demanding. Once through, this function parses the url for the access token
 * and then stores it to be used later or when navigating away from the home page.
 */
function getAccessToken() {

    access_token = sessionStorage.getItem("accessToken");

    if (access_token === null) {
        if (window.location.hash) {
            console.log('Getting Access Token');

            var hash = window.location.hash.substring(1);
            var accessString = hash.indexOf("&");

            /* 13 because that bypasses 'access_token' string */
            access_token = hash.substring(13, accessString);
            console.log("Access Token: " + access_token);

            /* If first visit or regaining token, store it in session. */    
            if (typeof(Storage) !== "undefined") {
                /* Store the access token */
                sessionStorage.setItem("accessToken", access_token); // store token.

                /* To see if we need a new token later. */
                sessionStorage.setItem("tokenTimeStamp", secondsSinceEpoch);

                /* Token expire time */
                sessionStorage.setItem("tokenExpireStamp", secondsSinceEpoch + 3600);
                console.log("Access Token Time Stamp: "
                + sessionStorage.getItem("tokenTimeStamp")
                + " seconds\nOR: " + dateNowMS + "\nToken expires at: "
                + sessionStorage.getItem("tokenExpireStamp"));
            } else {
                alert("Your browser does not support web storage...\nPlease try another browser.");
            }
        } else {
            console.log('URL has no hash; no access token');
        }
    } else if (upTokenTime >= tokenExpireSec) {
        console.log("Getting a new acess token...Redirecting");

        /* Remove session vars so we dont have to check in implicitGrantFlow */
        sessionStorage.clear();

        $(location).attr('href', 'index.html'); // Get another access token, redirect back.

    } else {
        var timeLeft = (tokenExpireSec - upTokenTime);
        console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");
    }

在我从url中获取访问令牌后,我将令牌存储在会话存储中。 我使用了早期帖子中提到的过程,但是这里是完整的JavaScript代码。 如果在注释之后仍然不清楚,请告诉我。

现在我们已经获取并存储了访问令牌,我们现在可以进行api调用。 这是我的做法(我一直在使用qQuery,以下是获取用户顶部曲目的示例)。

示例api调用

/**
 * Function will get the user's top tracks depending on the limit and offset
 * specified in addition to the time_range specified in JSON format.
 * @param time_range short/medium/long range the specifies how long ago.
 * @param offset Where the indexing of top tracks starts.
 * @param limit How many tracks at a time we can fetch (50 max.)
 */
function getUserTopTracks(time_range, offset, limit) {

$.get({
    url: 'https://api.spotify.com/v1/me/top/tracks',
    headers: {
        'Authorization': 'Bearer ' + access_token,
    },
    data: {
        limit: limit, // This is how many tracks to show (50 max @ a time).
        offset: offset, // 0 = top of list, increase to get more tracks.
        time_range: time_range // short/medium/long_term time ranges.
    },
    success: function (response) {

        /* Get the items from the response (The limit) tracks. */
        res = JSON.parse(JSON.stringify(response.items));

        /* Get all the track details in the json */
        for (i = 0; i < res.length; i++) {
            console.log("Track: " + res[i]);
        }
    },
    fail: function () {
        console.log("getUserTopTracks(): api call failed!");
    }
});

参数time_range被指定为“long_term”,以获取用户从一开始就喜欢的顶级曲目(有关更多信息,请查阅Spotify的文档),此外,偏移量为0以从头开始,并且限制为50,因为这是每次调用的最大获取数量。

成功后,我的响应变量'response'中的根要从“items”部分开始解析,以使解析更容易(您不必这样做,您可以简单地使用response.xxx.items.xxx)。然后我将响应打印到控制台中。

这是您可以做的基本操作,您决定如何处理数据或存储数据取决于您。我不是专家,我仅在上个学期开始学习Web编程,我所做的许多实践可能是错误或不正确的。


你有使用过的JS代码示例吗?我不理解的部分是如何获取令牌。我的弹出窗口打开,用户登录,然后我让它重定向回CodePen,弹出窗口现在不会自动关闭,但是一旦用户登录,我想关闭弹出窗口。我应该为弹出窗口发送自己的JavaScript代码来解析URL吗?还是有一些方法可以添加事件监听器函数,以便一旦访问带有令牌的重定向URL,我就运行字符串解析内容以仅获取访问令牌? - Martin
@Martin 我知道发生了什么。请参考这个链接:https://dev59.com/H2Ij5IYBdhLWcg3wpmoH 以获得更详细的解释。基本上,为了在服务器上完成之前在本地机器上进行测试,我安装了Firefox的“CORS Everywhere”插件。如果您没有使用Firefox,则我相信Chrome也有类似的插件。启用后,它将绕过CORS策略,并允许您检查请求是否实际起作用。尝试一下,看看是否有效。 - destrada
谢谢你的帮助,我已经成功在我的页面上生成/保存了访问令牌。我正在尝试调用您的函数 'getUserTopTracks('long_term', 0, 15)' 来获取热门曲目的API调用。我检查了我的访问令牌字符串是否包含在get请求中,但我收到了403控制台错误:GET https://api.spotify.com/v1/me/top/tracks?limit=15&offset=0&time_range=long_term 403 - Martin
它可能是我的作用域,目前设置为“playlist-modify-public”。 - Martin
是的,请更新您的范围。您需要包含“user-library-modify”。 - destrada
显示剩余7条评论

0

你走在正确的道路上。

弹出窗口会将您重定向到您添加的网站下的redirect_uri=...。它将向该网址添加代码和状态查询参数。

因此,在充当您的redirect_uri主机的网页上,您可以解析完整的URL。

无法在单个页面上执行此操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接