You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无刷新令牌时,如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:36:23