ReactJS-使用iframe静默更新令牌 [英] ReactJS - Silently renew token with iframe

查看:100
本文介绍了ReactJS-使用iframe静默更新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将身份验证过程包装起来,并每60分钟在我的React应用程序中实现一个无提示令牌续订,如下所示:

I'm trying to wrap my had around authentication processes, and implement, every 60 minutes, a silent token renew in my React app, like so:


  1. 创建一些监视程序功能,该功能检查
    访问令牌的到期时间。如果令牌即将过期,就该更新令牌了。

  2. 呈现一个iframe代码,该src应该是您与
    用于重定向到的相同URL。身份验证服务器,有一个区别:
    将返回URL更改为静态文件,我们将其称为redirect.html。
    服务器应该从存储的cookie中知道有关用户正在调用此URL的信息,因此它应该只是将您重定向到
    redirect.html文件,现在有了新的访问令牌。

  3. 在此redirect.html中编写一个简短的脚本,该脚本将从URL中取出
    令牌,并用本地存储中已有的令牌覆盖它。

  4. 销毁iframe。

  1. Create some watcher function, which checks the expiration time of the access token. If the token is about to expire, it is time to renew it.
  2. Render an iframe tag, the src should be the same URL which you are using for redirecting to the Auth server, with one difference: change the return URL to a static file, let's call it redirect.html. The server should know about the user calling this URL, from the stored cookie, so it should just simply redirect you to the redirect.html file, now with a fresh access token.
  3. In this redirect.html write a short script, which takes out the token from the URL and override it with the one you already have in local storage.
  4. Destroy the iframe.








Spotify页面:

Spotify page:

在Spotify开发人员页面上,我保存了我通常的重定向URL,用于首次从授权URL服务器获取令牌的时间:

At Spotify dev page, I have my usual redirection URL saved, for when the token is first fetched from authorization URL server:

http:// localhost

然后我添加

http:// localhost / redirect_html


App

App

App.jsx

这是我拥有的组件到目前为止,我一直在进行静默更新,在我的父组件的 localhost / test-silent-renew 中进行测试,如下所示:

This is the component I have for silent renewal, so far, which I'm testing at localhost/test-silent-renew at my parent component, like so:

 <Route exact path='/test-silent-renew' render={() => (
     <SilentTokenRenew
     />
 )} />








组件

Component

这是实际的刷新组件:

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

HTML

这是我的iframe的代码:

And this is the code for my 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>






我可以看到,如果执行以下操作:


I can see that if I do the following:

url.searchParams.set('prompt', 'none')
window.location = url; /// <-------

新令牌位于浏览器网址中

The new token is there, at browser url redirect.

但是我似乎无法使< script> localhost / redirect.html 文件位置,该位置与组件位于同一根目录下。

But I don't seem to be able to make my <script> work at localhost/redirect.html file location, which is under the same root as the component.

必须存在一些问题我的 redirect.html 脚本或文件,设置了我的 requestKey ,因为我正在控制台记录 redirectUrl undefined null ,如果我使用

There must be some problem with my redirect.html script, or file, setting my requestKey, because I'm console logging redirectUrl as undefined or null, if I use either


  1. const redirectUrl = localStorage.getItem('silent-redirect-url-key')

const redirectUrl = localStorage ['silent-redirect-url -key']








编辑

EDIT

Chrome保持沉默,但Firefox告诉我:

Chrome goes silent, but Firefox tells me that:

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 代理,其中客户端的配置如下:

I have a nginx proxy, where client is configured like so:

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配置

Is there a workaround for that limitation changing my nginx config above?

或者,如果没有,并且只有在没有框架的情况下,我才会定期获取此新令牌,如下所示:

Or, if not, and only without framing I seem to periodically get this new token, like so:

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 // <----

我可以在不影响应用程序可用性的情况下使用刮取等方式提取此令牌吗?

Can I extract this token with no prejudice to app usability, using scraping etc?

推荐答案


  1. silenwRenew 方法中, redirectUrl 需要从 localStorage 中检索,这就是您要存储在相同键下的 redirect.html 文件中的URL。因此,创建一个将用于两个部分的密钥。例如:

  1. In silenwRenew method, the redirectUrl needs to be retrieved from the localStorage, this is the URL what you are going to store in the redirect.html file, under the same key. So, create a key which you will use for both parts. For example:

const redirectUrl = localStorage ['silent-derirect-url-key']


  • 您可能需要使用 localStorage.getItem('silent-derirect-url-key') ,但应同时适用于这两种方式。

  • you may need to use localStorage.getItem('silent-derirect-url-key'), but it should work with both ways.

redirect.html 文件,使用相同的密钥,因此要设置商店的URL,请使用:

In redirect.html file, use the same key, so for setting the URL to the store, use:

const requestKey ='silent-derirect-url -key'

最后,方法 extractTokenFromUrl 应该很简单,像这样:

Lastly, the method extractTokenFromUrl should be simple, something like this:

extractTokenFromUrl(redirectUrl = '') {
    let accessToken = null
    let decodedAccessToken = null
    const accessTokenMatch = redirectUrl.match(/(?:[?&#/]|^)access_token=([^&]+)/)
    if (accessTokenMatch) {
      accessToken = accessTokenMatch[1]
      decodedAccessToken = JSON.parse(atob(accessToken.split('.')[1]))
    }

    let expireDurationSeconds = 3600
    const expireDurationSecondsMatch = redirectUrl.match(/expires_in=([^&]+)/)
    if (expireDurationSecondsMatch) {
      expireDurationSeconds = parseInt(expireDurationSecondsMatch[1], 10)
    }

    return {
      accessToken,
      decodedAccessToken,
      expireDurationSeconds,
    }
  }

Ofc可以使代码更好,但您可以理解。

Ofc you can make the code nicer, but you get the idea.

这篇关于ReactJS-使用iframe静默更新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆