前端基于JS与Axios实现每次API请求携带Firebase认证令牌的问题排查
前端端侧问题解决方案(解决令牌异步抢跑、返回Promise的核心问题)
核心问题根因定位
之前遇到的两类异常本质上是同一个原因:
- Axios默认会按代码顺序执行请求动作,异步获取令牌的流程没有被纳入请求的前置阻塞流程,导致请求先于令牌获取完成发出
- 调用异步的
getIdToken方法时,没有在async作用域内用await接收返回值,直接把Promise对象当成令牌字符串塞到请求头里
第一步:封装带缓存逻辑的统一令牌获取方法
不要把令牌获取逻辑散落在各个交互事件里,在token.js里封装全局可复用的异步方法,自带本地缓存减少Firebase接口调用次数:
// 全局缓存变量,避免重复拉取令牌 let cachedIdToken = null; let tokenValidUntil = 0; export async function fetchValidIdToken() { // 优先使用本地未过期的令牌,提前5分钟触发刷新,避免请求过程中令牌刚好失效 const now = Date.now(); if (cachedIdToken && now < tokenValidUntil - 5 * 60 * 1000) { return cachedIdToken; } const currentFirebaseUser = firebase.auth().currentUser; // 无登录态时直接抛出异常,不返回空值 if (!currentFirebaseUser) { throw new Error("未检测到有效登录态"); } // 强制拉取最新有效令牌,不使用本地可能过期的缓存 const freshToken = await currentFirebaseUser.getIdToken(true); // 解析令牌中的过期时间戳,更新本地缓存 const tokenPayload = JSON.parse(atob(freshToken.split(".")[1])); cachedIdToken = freshToken; tokenValidUntil = tokenPayload.exp * 1000; return freshToken; }
第二步:配置Axios全局请求拦截器,从流程上杜绝令牌抢跑
所有后端请求都通过统一配置的Axios实例发出,在拦截器里完成令牌获取、请求头拼接的全流程,拦截器内部的异步流程会阻塞请求的实际发送动作,直到令牌拿到再继续执行,从根源上解决请求先跑、令牌为空的问题:
// 全局Axios实例初始化 import axios from "axios"; import { fetchValidIdToken } from "./token.js"; const apiClient = axios.create({ baseURL: "/api", // 匹配Django后端的路由前缀 timeout: 10000 }); // 请求拦截器:所有发出的请求都会先经过这里的逻辑处理 apiClient.interceptors.request.use(async (requestConfig) => { try { // 等待令牌获取完成,拿到真实的字符串值后再拼接请求头 const idToken = await fetchValidIdToken(); // 按照Django后端的鉴权要求,设置Authorization请求头,格式为Bearer 令牌值 requestConfig.headers.Authorization = `Bearer ${idToken}`; } catch (err) { // 令牌获取失败时直接中断请求,跳转登录页 console.error("身份令牌获取失败", err); window.location.href = "/login"; return Promise.reject(err); } return requestConfig; }, (error) => { return Promise.reject(error); }); // 导出配置完成的Axios实例,业务代码直接引用使用 export default apiClient;
业务代码的正确调用方式
对比之前容易踩坑的写法,业务代码里不需要手动处理令牌逻辑,直接调用封装好的Axios实例即可:
- 错误写法(之前踩过的坑)
// 按钮点击事件里的错误实现:直接把异步函数返回的Promise当令牌使用 document.querySelector("#submit-btn").addEventListener("click", () => { const tokenPromise = fetchValidIdToken(); axios.post("/business/submit", { formData: "test" }, { headers: { Authorization: `Bearer ${tokenPromise}` } // 此处传入的是Promise对象,不是真实令牌 }) }) - 正确写法
// 按钮点击事件直接调用封装好的apiClient,无需手动处理令牌 document.querySelector("#submit-btn").addEventListener("click", async () => { try { const response = await apiClient.post("/business/submit", { formData: "test" }); // 处理业务返回结果 console.log("请求成功", response.data); } catch (err) { console.error("请求失败", err); } })
Django后端的兼容配置
因为前端请求头传的是Bearer {令牌字符串}的格式,后端提取令牌时需要去掉前缀再做校验,避免verify_id_token解码失败:
# Django鉴权逻辑示例 from firebase_admin import auth from rest_framework import authentication class FirebaseAuthMiddleware(authentication.BaseAuthentication): def authenticate(self, request): auth_header = request.META.get("HTTP_AUTHORIZATION") if not auth_header: return None # 拆分出Bearer后面的真实令牌字符串 id_token = auth_header.split("Bearer ")[1] try: # 调用firebase_admin的接口解码校验 decoded_result = auth.verify_id_token(id_token) uid = decoded_result["uid"] # 返回认证通过的用户和对应标识 return (request.user, uid) except Exception: raise authentication.errors.AuthenticationFailed("身份令牌校验不通过") return None
内容的提问来源于stack exchange,提问作者vignesh
相关产品推荐
相关产品推荐

