无刷新令牌时,如何用RTK Query保持用户登录状态
一、选型结论:优先用Cookie(相对更安全)
在只能二选一的前提下,Cookie比LocalStorage更适合存这个长有效期的access token,原因如下:
- Cookie可配置
HttpOnly+Secure+SameSite=Strict/Lax属性,能直接防范XSS攻击窃取token;LocalStorage完全暴露在JS环境中,XSS脚本可直接读取,风险更高 - 如果后端支持,尽量让后端在登录接口响应时直接设置Cookie(带上上述安全属性),前端无需手动存储;若必须前端存,手动设置Cookie时一定要加上这些属性
二、结合RTK Query的具体实现步骤
1. 登录成功后存储token
修改authApiSlice中login mutation的onQueryStarted钩子,登录成功后提取token并存入Cookie(或LocalStorage):
login: builder.mutation({ query: (credentials: MetadataObj) => ({ url: "/signin", method: "POST", body: { ...credentials }, }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; const token = data.accessToken; // 假设后端返回的token字段是accessToken // 存Cookie(推荐,带上安全属性) document.cookie = `accessToken=${token}; path=/; max-age=31536000; Secure; HttpOnly; SameSite=Strict`; // 若必须用LocalStorage(不推荐),替换为下面一行 // localStorage.setItem('accessToken', token); // 同步更新authSlice的登录状态(需确保authSlice有对应的action,比如setCredentials) // dispatch(setCredentials({ token })); } catch (err) { console.log(err); } }, }),
2. 全局请求自动携带token
在根apiSlice的prepareHeaders方法中,每次请求前自动从Cookie(或LocalStorage)提取token,添加到请求头:
// 根apiSlice的创建代码示例 export const apiSlice = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: '/api', prepareHeaders: (headers) => { // 从Cookie提取token const token = document.cookie.split('; ') .find(row => row.startsWith('accessToken=')) ?.split('=')[1]; // 若用LocalStorage,替换为下面一行 // const token = localStorage.getItem('accessToken'); if (token) { headers.set('Authorization', `Bearer ${token}`); } return headers; }, }), endpoints: builder => ({ /* ... */ }), });
3. 登出时清除token
修改sendLogout mutation的onQueryStarted钩子,登出成功后清除存储的token:
sendLogout: builder.mutation({ query: () => ({ url: "/logout", method: "POST", }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { await queryFulfilled; dispatch(logOut(null)); dispatch(apiSlice.util.resetApiState()); // 清除Cookie document.cookie = `accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`; // 若用LocalStorage,替换为下面一行 // localStorage.removeItem('accessToken'); } catch (err) { console.log(err); } }, }),
4. 页面刷新恢复登录状态
在应用初始化时(比如App组件的useEffect中),检查token是否存在,若存在则同步到Redux的auth状态,保证页面刷新后UI保持登录态:
// App.tsx示例 import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { setCredentials } from './features/auth/authSlice'; function App() { const dispatch = useDispatch(); useEffect(() => { const token = document.cookie.split('; ') .find(row => row.startsWith('accessToken=')) ?.split('=')[1]; // 若用LocalStorage,替换为下面一行 // const token = localStorage.getItem('accessToken'); if (token) { dispatch(setCredentials({ token })); } }, [dispatch]); return ( {/* 应用内容 */} ); } export default App;
三、额外风险规避建议
- 在RTK Query的baseQuery中添加错误拦截:若请求返回401(token失效),自动触发登出流程并清除token
- 定期提示用户修改密码,降低长期token泄露带来的账户风险
- 若后端允许,尽量让token的Cookie属性由后端配置(避免前端手动设置时遗漏安全属性)
内容的提问来源于stack exchange,提问作者Ruby
相关产品推荐
相关产品推荐

