【发布时间】:2020-06-06 10:35:03
【问题描述】:
我正在尝试围绕身份验证过程进行包装,并在我的 React 应用程序中每 60 分钟实现一次静默令牌更新,如下所示:
- 创建一些watcher函数,检查过期时间 访问令牌。如果令牌即将过期,是时候更新它了。
- 渲染一个 iframe 标签,src 应该是你的同一个 URL 用于重定向到 Auth 服务器,有一个区别: 将返回 URL 更改为静态文件,我们称之为 redirect.html。 服务器应该知道用户调用这个 URL,从存储的 cookie,所以它应该只是简单地将你重定向到 redirect.html 文件,现在带有新的访问令牌。
- 在此 redirect.html 中编写一个简短的脚本,将 来自 URL 的令牌,并用您在本地存储中已有的令牌覆盖它。
- 销毁 iframe。
Spotify 页面:
在 Spotify 开发页面,我保存了我常用的重定向 URL,用于首次从授权 URL 服务器获取令牌时:
http://localhost
然后我还使用 Spotify 为我的 iframe 添加了一个新的重定向 URL:
http://localhost/redirect_html
应用程序
App.jsx
到目前为止,这是我用于静默更新的组件,我正在父组件的 localhost/test-silent-renew 上对其进行测试,如下所示:
<Route exact path='/test-silent-renew' render={() => (
<SilentTokenRenew
/>
)} />
组件
这是真正的刷新组件:
SilentTokenRenew.jsx
import React, { Component } from 'react'
class SilentTokenRenew extends Component {
constructor(props) {
super(props)
this.state = {
renewing: false,
isAuthenticated: false
}
this.currentAttempt = 0
this.maxNumberOfAttempts = 20
this.state.renderIframe = this.renderIframe.bind(this);
this.state.handleOnLoad = this.handleOnLoad.bind(this);
};
shouldComponentUpdate(nextProps, nextState) {
return this.state.renewing !== nextState.renewing
}
componentDidMount() {
this.timeInterval = setInterval(this.handleCheckToken, 20000)
}
componentWillUnmount() {
clearInterval(this.timeInterval)
}
willTokenExpire = () => {
const accessToken = localStorage.getItem('spotifyAuthToken');
console.log('access_token', accessToken)
const expirationTime = 3600
const token = { accessToken, expirationTime } // { accessToken, expirationTime }
const threshold = 300 // 300s = 5 minute threshold for token expiration
const hasToken = token && token.accessToken
const now = (Date.now() / 1000) + threshold
console.log('NOW', now)
return !hasToken || (now > token.expirationTime)
}
handleCheckToken = () => {
if (this.willTokenExpire()) {
this.setState({ renewing: true })
clearInterval(this.timeInterval)
}
}
silentRenew = () => {
return new Promise((resolve, reject) => {
const checkRedirect = () => {
// This can be e
const redirectUrl = localStorage.getItem('silent-redirect-url-key')
console.log('REDIRECT URL', redirectUrl)
if (!redirectUrl) {
this.currentAttempt += 1
if (this.currentAttempt > this.maxNumberOfAttempts) {
reject({
message: 'Silent renew failed after maximum number of attempts.',
short: 'max_number_of_attempts_reached',
})
return
}
setTimeout(() => checkRedirect(), 500)
return
}
// Clean up your localStorage for the next silent renewal
localStorage.removeItem('silent-redirect-url-key') // /redirect.html#access_token=......
// // Put some more error handlers here
// // Silent renew worked as expected, lets update the access token
const session = this.extractTokenFromUrl(redirectUrl) // write some function to get out the access token from the URL
// // Following your code you provided, here is the time to set
// // the extracted access token back to your localStorage under a key Credentials.stateKey
localStorage.setItem(Credentials.stateKey, JSON.stringify(session))
resolve(session)
}
checkRedirect()
})
}
handleOnLoad = () => {
this.silentRenew()
.then(() => {
this.setState({ renewing: false })
this.currentAttempt = 0
this.timeInterval = setInterval(this.handleCheckToken, 60000)
// Access token renewed silently.
})
.catch(error => {
this.setState({ renewing: false })
// handle the errors
})
}
generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
renderIframe = () => {
const state = this.generateRandomString(16);
const url = new URL('https://accounts.spotify.com/authorize?response_type=token&client_id=my_id&scope=user-read-currently-playing%20user-read-private%20user-library-read%20user-read-email%20user-read-playback-state%20user-follow-read%20playlist-read-private%20playlist-modify-public%20playlist-modify-private&redirect_uri=http%3A%2F%2Flocalhost&state=rBZaR9s1gHchWEME')
console.log('URL HREF', url.href)
console.log(url.searchParams.get('redirect_uri'))
url.searchParams.set(Credentials.stateKey, state)
url.searchParams.set('redirect_uri', 'http://localhost/redirect.html') // the redirect.html file location
url.searchParams.set('prompt', 'none')
//window.location = url;
return (
<iframe
style={{ width: 0, height: 0, position: 'absolute', left: 0, top: 0, display: 'none', visibility: 'hidden' }}
width={0}
height={0}
title="silent-token-renew"
src={url.href}
onLoad={this.handleOnLoad}
/>
)
}
render() {
const { renewing } = this.state
return renewing ? this.renderIframe() : null
}
}
export default SilentTokenRenew;
HTML
这是我的 iframe 的代码:
<!DOCTYPE html>
<html>
<head>
<title>OAuth - Redirect</title>
</head>
<body>
<p>Renewing...</p>
<script>
// Get name of window which was set by the parent to be the unique request key
// or if no parameter was specified, we have a silent renew from iframe
const requestKey = 'silent-redirect-url-key'
// Update corresponding entry with the redirected url which should contain either access token or failure reason in the query parameter / hash
window.localStorage.setItem(requestKey, window.location.href);
window.close();
</script>
</body>
</html>
如果我执行以下操作,我可以看到:
url.searchParams.set('prompt', 'none')
window.location = url; /// <-------
新令牌在那里,在浏览器 url 重定向处。
但我似乎无法让我的 <script> 在与组件位于同一根目录下的 localhost/redirect.html 文件位置工作。
我的redirect.html 脚本或文件一定有问题,设置我的requestKey,因为我将控制台记录redirectUrl 为undefined 或null,如果我使用任何一个
-
const redirectUrl = localStorage.getItem('silent-redirect-url-key')或
-
const redirectUrl = localStorage['silent-redirect-url-key']
编辑
Chrome 沉默了,但 Firefox 告诉我:
Load denied by X-Frame-Options: “deny” from “https://accounts.spotify.com/login?continue=https%3A%2F%2Fac…prompt%3Dnone%26client_id%my_id”, site does not permit any framing. Attempted to load into “http://localhost/test”
我有一个nginx 代理,其中客户端配置如下:
server {
listen 80;
location / {
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
location /redirect.html {
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
改变我上面的 nginx 配置是否有解决该限制的方法?
或者,如果不是,并且只有在没有框架的情况下,我似乎会定期获得这个新令牌,如下所示:
const url = new URL('https://accounts.spotify.com/authorize?response_type=token&client_id=my_id&scope=user-read-currently-playing%20user-read-private%20user-library-read%20user-read-email%20user-read-playback-state%20user-follow-read%20playlist-read-private%20playlist-modify-public%20playlist-modify-private&redirect_uri=http%3A%2F%2Flocalhost&state=rBZaR9s1gHchWEME')
window.location = url // <----
我可以在不影响应用可用性的情况下使用抓取等方法提取此令牌吗?
【问题讨论】:
-
这可能是你的情况:
return !hasToken || (now > token.accessToken.expirationTime)应该是:return !hasToken || (now > token.expirationTime)inSilentTokenRenew .willTokenExpire()吗? -
这里到底是什么问题,你不能重定向到那个
redirect.html或者你被重定向了,但是你需要帮助编写脚本来解析令牌并将其存储到LS?跨度>
标签: javascript reactjs access-token