Keycloak刷新令牌后React Query使用过期JWT请求头问题
React Query 结合 Keycloak 令牌刷新后请求头未更新导致401问题
问题背景
- 项目技术栈:React Native、React Query、Axios,采用Keycloak作为认证管理器,通过
react-native-keycloak库管理全局认证状态 - 故障现象:应用运行一段时间后,服务端会随机对请求返回401响应,部分场景下会直接导致应用崩溃
- 复现方式:将Keycloak的Bearer Token过期时间设置为1分钟即可稳定复现问题,配置完成后几乎立刻会触发401响应
现有业务实现
以Activities列表页的请求逻辑为例,页面加载时通过封装useQuery的自定义Hook拉取数据:
export function useActivities(): UseQueryResult<ActivityList> { const { headers } = useHeaders(); return useQuery( ['activities', today.start], () => getActivitiesList(headers), // Note 1 { enabled: !!today.start, } ); }
项目中统一通过useHeaders Hook获取携带最新Keycloak令牌、域配置的请求头,该Hook在全项目被广泛调用:
export function useHeaders(): UseHeaders { const { keycloak } = useKeycloak(); const KEYCLOAK_REALM = remoteConfig().getString('KEYCLOAK_REALM'); const headers = { Authorization: `Bearer ${keycloak?.token}`, Realm: KEYCLOAK_REALM, }; return { headers }; }
列表请求的核心方法实现如下:
async function getActivitiesList(headers: UseHeaders['headers']): Promise<ActivityList> { const url = `${BASE_URL}${VERSION}/activities/grouped?end=${end}&start=${start}`; // Note 2 return axios .get(url, { headers }) .then((res) => res.data) .catch((e) => console.error('Error fetching grouped activities:', e)); }
核心异常特征
当Keycloak触发令牌刷新逻辑后,keycloak对象内存储的token已更新,useActivities中获取的headers值也已更新,但无论是query函数内(注释Note 1位置)还是getActivitiesList内部(注释Note 2位置)打印的headers都未更新,仍为携带旧令牌的请求头。
该异常出现时存在两种表现:
- 触发两次重复请求,第一次携带旧token请求失败报错,第二次携带新token请求正常返回
- 直接导致应用无理由崩溃
目前无法定位query函数未同步更新请求头、始终向getActivitiesList传递旧headers值的根本原因。
临时缓解方案
目前通过修改Axios全局配置的方式临时降低问题出现概率:
- Keycloak初始化完成后,立即配置全局域请求头:
axios.defaults.headers.common.Realm = KEYCLOAK_REALM; - 每次从Keycloak获取到有效令牌后,覆写全局Authorization请求头:
axios.defaults.headers.common.Authorization = 'Bearer ${keycloak?.token}';
该方案存在明显缺陷,需要找到在useQuery逻辑中正确处理令牌刷新的标准实现方案。
内容的提问来源于stack exchange,提问作者Martinocom
相关产品推荐
相关产品推荐

