gpt4 book ai didi

ReactJS - 观看访问 token 过期

转载 作者:行者123 更新时间:2023-12-04 13:15:03 29 4
gpt4 key购买 nike

在我的应用程序中,我有一个必须始终有效的访问 token (Spotify 的)。当此访问 token 过期时,应用必须每 60 分钟刷新一次 token 端点并获取另一个访问 token 。

Authorize functions

出于安全原因,这两个对 /get_token/refresh_token 的调用由 python 处理,服务器端,状态目前正在我的父级处理 App.jsx,像这样:

class App extends Component {
constructor() {
super();
this.state = {
users: [],
isAuthenticated: false,
isAuthorizedWithSpotify: false,
spotifyToken: '',
isTokenExpired:false,
isTokenRefreshed:false,
renewing: false,
id: '',
};

componentDidMount() {
this.userId(); //<--- this.getSpotifyToken() is called here, inside then(), after async call;
};

getSpotifyToken(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/get_token/${this.state.id}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`,
}
};
// needed for sending cookies
axios.defaults.withCredentials = true
return axios(options)
.then((res) => {
console.log(res.data)
this.setState({
spotifyToken: res.data.access_token,
isTokenExpired: res.data.token_expired // <--- jwt returns expiration from server
})
// if token has expired, refresh it
if (this.state.isTokenExpired === true){
console.log('Access token was refreshed')
this.refreshSpotifyToken();
}
})
.catch((error) => { console.log(error); });

};

refreshSpotifyToken(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/refresh_token/${this.state.id}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`,
}
};
axios.defaults.withCredentials = true
return axios(options)
.then((res) => {
console.log(res.data)
this.setState({
spotifyToken: res.data.access_token,
isTokenRefreshed: res.data.token_refreshed,
isTokenExpired: false,
isAuthorizedWithSpotify: true
})
})
.catch((error) => { console.log(error); });
};

然后,我将 this.props.spotifyToken 传递给我的所有子组件,其中使用访问 token 发出请求,一切正常。


Watcher Function

问题是,当应用程序在给定页面上保持空闲超过 60 分钟并且用户发出请求时,这会发现访问 token 已过期,并且其状态不会是已更新,因此请求将被拒绝。

为了解决这个问题,我考虑在 App.jsx 中设置一个在后台跟踪 token 过期时间的观察者函数,如下所示:

willTokenExpire = () => {
const accessToken = this.state.spotifyToken;
console.log('access_token in willTokenExpire', accessToken)
const expirationTime = 3600
const token = { accessToken, expirationTime } // { accessToken, expirationTime }
const threshold = 300 // 300s = 5 minute threshold for token expiration

const hasToken = token && token.spotifyToken
const now = (Date.now() / 1000) + threshold
console.log('NOW', now)
if(now > token.expirationTime){this.getSpotifyToken();}
return !hasToken || (now > token.expirationTime)
}

handleCheckToken = () => {
if (this.willTokenExpire()) {
this.setState({ renewing: true })
}
}

和:

shouldComponentUpdate(nextProps, nextState) {
return this.state.renewing !== nextState.renewing
}

componentDidMount() {
this.userId();
this.timeInterval = setInterval(this.handleCheckToken, 20000)
};

Child component

然后,从 Parent App.jsx 中的 render(),我将传递 handleCheckToken() 作为回调函数,以及 this.props。 spotifyToken,给可能空闲的子组件,像这样:

<Route exact path='/tracks' render={() => (
<Track
isAuthenticated={this.state.isAuthenticated}
isAuthorizedWithSpotify={this.state.isAuthorizedWithSpotify}
spotifyToken={this.state.spotifyToken}
handleCheckToken={this.handleCheckToken}
userId={this.state.id}
/>
)} />

在子组件中,我会:

class Tracks extends Component{
constructor (props) {
super(props);
this.state = {
playlist:[],
youtube_urls:[],
artists:[],
titles:[],
spotifyToken: this.props.spotifyToken
};
};

componentDidMount() {
if (this.props.isAuthenticated) {
this.props.handleCheckToken();
};
};

以及需要有效的、更新的 spotifyToken 的调用,如下所示:

  getTrack(event) {
const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/get-tracks/${userId}/${this.props.spotifyToken}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.message)
})
.catch((error) => { console.log(error); });
};

但这行不通。

定期使用 handleCheckToken 获取新 token ,即使我在 Child 空闲时也是如此。但是,如果我在 60 分钟后发出请求,在 Child 中,传递的 this.props.spotifyToken 已过期,因此 props 没有正确传递给 Child.jsx。

我错过了什么?

最佳答案

您正在谈论将 refreshToken 交换为 accessToken 机制,我认为您把它复杂化了。

一个背景,我有一个类似的设置,登录生成一个 accessToken(有效期为 10 分钟)和一个 refreshToken 作为 cookie 在 refreshToken 端点(不是必要的)。

然后我的所有组件都使用一个简单的 api 服务(它是 Axios 的包装器)以便向服务器发出 ajax 请求。我的所有端点都希望获得有效的 accessToken,如果它过期,它们会返回 401 并显示过期消息。我的 Axios 有一个响应 interceptor它检查响应是否具有状态 401 和特殊消息,如果是,它向 refreshToken 端点发出请求,如果 refreshToken 有效(12 小时后过期) ) 它返回一个 accessToken,否则返回 403。拦截器获取新的 accessToken 并重试(最多 3 次)先前失败的请求。

很酷的想法是,通过这种方式,accessToken 可以保存在内存中(不是localStorage,因为它暴露于XSS 攻击)。我将它保存在我的 api 服务中,因此,没有组件会处理与 token 相关的任何事情。

另一个很酷的想法是它对完整页面重新加载也是有效的,因为如果用户有一个带有refreshToken的有效cookie,第一个api将失败并返回 401,整个机制将起作用,否则将失败。

// ApiService.js

import Axios from 'axios';

class ApiService {
constructor() {
this.axios = Axios.create();
this.axios.interceptors.response.use(null, this.authInterceptor);

this.get = this.axios.get.bind(this.axios);
this.post = this.axios.post.bind(this.axios);
}

async login(username, password) {
const { accessToken } = await this.axios.post('/api/login', {
username,
password,
});
this.setAccessToken(accessToken);
return accessToken; // return it to the component that invoked it to store in some state
}

async getTrack(userId, spotifyToken) {
return this.axios.get(
`${process.env.REACT_APP_WEB_SERVICE_URL}/get-tracks/${userId}/${spotifyToken}`
);
}

async updateAccessToken() {
const { accessToken } = await this.axios.post(`/api/auth/refresh-token`, {});
this.setAccessToken(accessToken);
}

async authInterceptor(error) {
error.config.retries = error.config.retries || {
count: 0,
};

if (this.isUnAuthorizedError(error) && this.shouldRetry(error.config)) {
await this.updateAccessToken(); // refresh the access token
error.config.retries.count += 1;

return this.axios.rawRequest(error.config); // if succeed re-fetch the original request with the updated accessToken
}
return Promise.reject(error);
}

isUnAuthorizedError(error) {
return error.config && error.response && error.response.status === 401;
}

shouldRetry(config) {
return config.retries.count < 3;
}

setAccessToken(accessToken) {
this.axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`; // assign all requests to use new accessToken
}
}

export const apiService = new ApiService(); // this is a single instance of the service, each import of this file will get it

这个机制是基于this article

现在有了这个 ApiService,您可以创建一个实例并将其导入到每个要发出 api 请求的组件中。

import {apiService} from '../ApiService';

class Tracks extends React.Component {
constructor(props) {
super(props);
this.state = {
playlist: [],
youtube_urls: [],
artists: [],
titles: [],
spotifyToken: this.props.spotifyToken,
};
}

async componentDidMount() {
if (this.props.isAuthenticated) {
const {userId, spotifyToken} = this.props;
const tracks = await apiService.getTracks(userId, spotifyToken);
this.setState({tracks});
} else {
this.setState({tracks: []});
}
}

render() {
return null;
}
}

编辑(回复评论)

  1. 登录流程的处理也可以使用此服务完成,您可以从登录 api 中提取 accessToken,将其设置为默认 header 并将其返回给调用者(这可能会将其保存在其他组件逻辑的状态中例如条件渲染)(更新了我的代码片段)。
  2. 只是一个需要使用api的组件示例。
  3. 只有一个 ApiService 实例是在文件的“模块”中创建的(最后您可以看到 new ApiService),之后您只需将这个导出的实例导入到所有需要进行 api 调用的地方。

关于ReactJS - 观看访问 token 过期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61347944/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com