React Native对接.NET后端刷新token偶发旧token复用及状态码0问题求助
问题背景
我有一个React Native应用,通过微软基于token的身份验证与.NET Framework后端(Web API)通信。token有效期为30分钟,过期后首次调用接口时服务端会返回401,此时会调用刷新token接口生成新的刷新token供应用使用。等待刷新token返回的过程中,客户端会将其他请求入队,拿到新token后再用新token重新发起这些请求。
疑难问题
服务端有时会返回状态码0,暂时未定位到根因。同时观察到的规律是:调用刷新token接口时,后端会删除旧token并生成新token,但应用发起新接口请求时有时仍使用旧token而非新token,该问题不是必现。是否是队列中的请求未成功入队,或是入队逻辑存在问题?
技术栈
- 服务端:4台运行IIS 10的专用服务器,通过负载均衡器分发请求
- 前端:React Native 0.64.2版本,采用
react-native-ssl-pinning发起请求
相关代码
基础请求方法
async request(route: string, action: string, params: any = null, isJson: boolean = true, ) { var options = await this.handleOption(action, params, isJson) route = Config.API_BASE_URI + route return new Promise(async resolve => { try { var response = await fetchSSLPinning(route, options) return resolve(response) } catch (e) { // // console.log(e) var originalRequest = { route, options, } var p = this.handleError(e, originalRequest) return resolve(p) } }) },
错误处理逻辑
async handleError(error: any, originalRequest: any): Promise<any> { const errorResponse = error if (this.isTokenExpiredError(errorResponse)) { return this.resetTokenAndReattemptRequest(error, originalRequest) } this.dispatchError(true, errorResponse.status) return Promise.reject(error) },
刷新token并重试逻辑
async resetTokenAndReattemptRequest(error: any, originalRequest: any) { try { const resetToken = await TokenStorage.getToken() if (!resetToken) { return Promise.reject(error) } const retryOriginalRequest = new Promise(resolve => { this.addSubscriber((accessToken: string) => { var options = { ...originalRequest.options, headers: { ...originalRequest.options.headers, authorization: 'Bearer ' + accessToken, }, } resolve(fetchSSLPinning(originalRequest.route, options)) }) }) if (!this.isAlreadyFetchingAccessToken) { this.isAlreadyFetchingAccessToken = true const refreshToken = await TokenStorage.getRefreshToken() const userName = await TokenStorage.getUsername() const response = await AuthenticationService.refreshToken( refreshToken, userName, ) if (!response) { this.isAlreadyFetchingAccessToken = false this.dispatchError(true, '404') return Promise.reject(error) } await TokenStorage.storeTokenInfo( response.access_token, response.refresh_token, response.userName, ) // save the newly refreshed token for other requests to use const newToken = response.access_token this.isAlreadyFetchingAccessToken = false this.onAccessTokenFetched(newToken) } return retryOriginalRequest } catch (err) { this.isAlreadyFetchingAccessToken = false this.dispatchError(true, err.status) return Promise.reject(err) } },
订阅者回调执行逻辑
onAccessTokenFetched(accessToken: string) { // When the refresh is successful, we start retrying the requests one by one and empty the queue this.subscribers.forEach((callback: any) => { callback(accessToken) }) this.subscribers = [] },
问题排查与解决方案
偶发旧token请求问题修复
现有代码存在2个核心隐患点:
- 刷新token过程中新增的请求未加锁:
handleOption方法获取token时,没有判断当前是否处于token刷新中,刷新过程中发起的新请求会直接从本地存储拿到旧token发起请求,不会走错误处理入队逻辑,这就是偶发旧token请求的最常见原因。
修复方案:在handleOption获取token前判断this.isAlreadyFetchingAccessToken状态,如果为true,直接将该请求加入订阅队列,不要直接用旧token发起请求。 isAlreadyFetchingAccessToken变量无原子性保护:多个异步请求同时触发401时,可能会同时进入resetTokenAndReattemptRequest,出现重复刷新token的问题,旧token被删除后,后触发的刷新请求用已失效的refreshToken调用接口会失败,导致后续队列请求也用旧token。
修复方案:将isAlreadyFetchingAccessToken的赋值操作放在订阅队列逻辑之前,确保第一个401请求进入后立刻锁定状态,后续请求直接入队。
状态码0问题排查
状态码0通常是跨域、网络中断、SSL证书验证失败、请求被拦截或者负载均衡超时导致的:
- 先排查负载均衡的会话保持配置:如果你的token验证是单节点内存存储,没有做分布式session共享,刷新token的请求落到A节点删除旧token,后续旧token请求落到B节点,就会出现偶发的验证失败或者异常响应。建议确认负载均衡是否开启了会话保持,或者后端token存储是否换成分布式缓存(如Redis)。
- 排查
react-native-ssl-pinning的证书配置:如果证书链不完整,或者证书更新后客户端没有同步,会出现偶发的SSL握手失败,返回状态码0。 - 检查IIS的请求超时配置:如果刷新token接口的处理时间超过IIS或者负载均衡的超时阈值,会被主动断开连接,返回状态码0。
内容的提问来源于stack exchange,提问作者mohsinali1317
相关产品推荐
相关产品推荐

