Apollo Client:避免过期令牌阻塞无授权请求的实现方案
解决Apollo Client令牌过期阻塞公开请求的方案
你的问题核心在于所有请求都强制带上了x-token头(哪怕是空值),后端可能会对这个头做校验,空令牌/过期令牌都会被判定为无效,进而阻塞公开数据的获取。要解决这个问题,我们需要让请求"按需携带令牌"——仅给需要授权的编辑类请求添加令牌头,公开查看类请求完全不携带这个头。
下面是两种实用的实现方式:
方式一:通过请求上下文(Context)标记是否需要授权
这是最灵活的方案,你可以在发起每个请求时明确指定是否需要授权:
1. 修改Apollo Client配置
修改request函数,仅当请求标记了requiresAuth: true时,才添加x-token头:
import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ uri: "http://localhost:8000/graphql", request: (operation) => { // 从请求上下文获取是否需要授权的标记 const { requiresAuth } = operation.getContext(); if (requiresAuth) { const token = sessionStorage.getItem('jwtToken'); operation.setContext({ headers: { 'x-token': token || '', }, }); } // 不需要授权的请求,不设置x-token头,避免后端校验 }, cache: new InMemoryCache(), });
2. 发起请求时标记授权需求
- 编辑类请求(需要授权):在
useMutation或useQuery中传入context: { requiresAuth: true }
import { useMutation } from '@apollo/client'; import UPDATE_POST_MUTATION from './mutations/UpdatePost'; const [updatePost] = useMutation(UPDATE_POST_MUTATION, { context: { requiresAuth: true }, // 标记需要授权 // 其他配置:variables、onCompleted等 });
- 查看类请求(无需授权):不需要传入
context参数,请求会自动不带x-token头
import { useQuery } from '@apollo/client'; import GET_POSTS_QUERY from './queries/GetPosts'; const { data, loading } = useQuery(GET_POSTS_QUERY); // 这个请求不会携带x-token头,后端不会校验令牌
方式二:通过操作名称(OperationName)自动区分
如果你的请求命名有统一规则(比如编辑类操作以Auth_开头),可以通过operationName自动判断是否需要授权:
修改Apollo Client配置
const client = new ApolloClient({ uri: "http://localhost:8000/graphql", request: (operation) => { // 检查操作名称是否属于需要授权的类型 if (operation.operationName?.startsWith('Auth_')) { const token = sessionStorage.getItem('jwtToken'); operation.setContext({ headers: { 'x-token': token || '', }, }); } }, cache: new InMemoryCache(), });
比如你的编辑mutation命名为Auth_UpdatePost,查看query命名为GetPosts,这样就能自动区分,无需手动标记上下文。
额外优化:拦截令牌过期错误
对于需要授权的请求,我们可以在错误链路中拦截令牌过期错误,做自动登出等处理,同时不影响公开请求:
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; // 创建HTTP链路 const httpLink = createHttpLink({ uri: "http://localhost:8000/graphql", }); // 创建错误拦截链路 const errorLink = onError(({ graphQLErrors, operation }) => { const { requiresAuth } = operation.getContext(); // 仅处理需要授权的请求的令牌过期错误 if (graphQLErrors && requiresAuth) { const isTokenExpired = graphQLErrors.some(err => err.message.includes('Token expired')); if (isTokenExpired) { // 清除过期令牌 sessionStorage.removeItem('jwtToken'); // 跳转到登录页 window.location.href = '/login'; } } // 公开请求的错误不做特殊处理,正常返回 }); // 组合链路 const client = new ApolloClient({ link: from([errorLink, httpLink]), cache: new InMemoryCache(), request: (operation) => { const { requiresAuth } = operation.getContext(); if (requiresAuth) { const token = sessionStorage.getItem('jwtToken'); operation.setContext({ headers: { 'x-token': token || '', }, }); } }, });
这样一来,公开请求不会携带令牌,也就不会被令牌过期错误阻塞;授权请求的令牌过期会被自动处理,不影响用户体验。
内容的提问来源于stack exchange,提问作者Kenzo
相关产品推荐
相关产品推荐

