axios 请求鉴权实战指南:Bearer Token、HTTP Basic、API Key 与 Cookie 会话方案
绝大多数 API 都需要某种形式的鉴权机制。本文围绕 axios 官方文档中的认证(Authentication)专题,系统讲解四种主流鉴权方案——Bearer Token(JWT)、HTTP Basic、API Key 与基于 Cookie 的会话认证——各自的配置写法与推荐用法,并结合 axios 仓库源码深入剖析 auth 配置项在不同适配器(fetch、http、xhr)下的真实处理链路,帮助你在实际项目中做出正确、安全且可维护的鉴权决策。
鉴权方式总览
在展开细节之前,先给出 axios 对四类常见鉴权方案的支持方式,方便快速定位:
| 鉴权方案 | axios 配置方式 | 底层机制 | 典型场景 |
|---|---|---|---|
| Bearer Token(JWT) | 请求拦截器中设置 Authorization 请求头 |
自定义请求头,每请求动态取值 | 前后端分离、OAuth2 / JWT 服务 |
| HTTP Basic | auth: { username, password } 选项 |
axios 自动编码并写入 Authorization: Basic ... |
内网 API、简单服务账号 |
| API Key | 实例默认请求头或 params 查询参数 |
普通请求头 / URL 参数 | 第三方开放平台 |
| Cookie / 会话 | withCredentials: true |
浏览器自动携带同源/跨域 Cookie | 同域站点会话、SSO |
核心原则(官方文档明确提示):auth 选项只用于 HTTP Basic 认证;Bearer Token 和 API Key 应通过自定义 Authorization 或其他请求头传递,不要滥用 auth。
Bearer Token(JWT):请求拦截器动态注入
前后端分离项目中,最常见的方式是把 JWT 放进 Authorization 请求头。官方推荐做法是在 axios 实例上挂请求拦截器,让 token 在每次发请求时实时读取(而非启动时缓存),从而天然规避 token 过期后缓存值失效的问题:
import axios from "axios";
const api = axios.create({ baseURL: "https://api.example.com" });
api.interceptors.request.use((config) => {
const token = localStorage.getItem("access_token");
if (token) {
config.headers.set("Authorization", `Bearer ${token}`);
}
return config;
});
几个实现要点:
axios.create()创建独立实例后,拦截器只作用于该实例,便于把鉴权逻辑收敛到 API 客户端层;config.headers是AxiosHeaders实例,使用.set()方法(而非直接赋值config.headers.Authorization)是与 axios 1.x 兼容的推荐写法;- 拦截器里读
localStorage保证 token 是发请求瞬间的最新值,这与后文"token 续期"方案配合使用。
HTTP Basic 认证:auth 选项与 URL 内嵌凭据
对于使用 HTTP Basic 认证的 API,直接传 auth 选项即可,axios 会完成 Base64 编码并自动设置 Authorization 头:
const response = await axios.get("https://api.example.com/data", {
auth: {
username: "myUser",
password: "myPassword",
},
});
类型定义上,index.d.ts 中声明了专门的结构(index.d.ts):
export interface AxiosBasicCredentials {
username: string;
password: string;
}
AxiosRequestConfig 与实例默认配置中都暴露了 auth?: AxiosBasicCredentials(见 index.d.ts)。
源码剖析:resolveConfig 中的 Basic 编码逻辑
auth 选项的核心处理位于配置解析阶段 lib/helpers/resolveConfig.js:
// HTTP basic authentication
if (auth) {
const username = utils.getSafeProp(auth, 'username') || '';
const password = utils.getSafeProp(auth, 'password') || '';
try {
headers.set(
'Authorization',
'Basic ' + btoa(username + ':' + (password ? encodeUTF8(password) : ''))
);
} catch (e) {
throw AxiosError.from(e, AxiosError.ERR_BAD_OPTION_VALUE, config);
}
}
从源码可以看出三个关键实现细节:
- 密码支持非 Latin-1 字符:密码会先经过 encodeUTF8(
encodeURIComponent转义 + 逐字节还原)转成 Latin-1 字节串再交给btoa(),因此open ßç£☃sesame这类非 ASCII 密码可以正确编码;而用户名若含非 Latin-1 字符会导致btoa抛错,并被包装成code: ERR_BAD_OPTION_VALUE的AxiosError。这两点都有测试佐证:tests/browser/basicAuth.browser.test.js 分别验证了非 Latin-1 密码的成功编码与非法用户名的报错,tests/unit/helpers/resolveConfig.test.js 验证了ERR_BAD_OPTION_VALUE包装。 - 只读取自有属性:
utils.getSafeProp(auth, 'username')配合own()机制只读取实例自有属性,专门防御原型链污染(如Object.prototype.username被恶意注入)。tests/unit/helpers/resolveConfig.test.js 中构造了Object.prototype.username继承场景,断言结果只包含auth: {}自身字段编码出的Basic Og==(即": "的 Base64),继承值被安全忽略。 - 编码失败不静默:Base64 编码异常会被包装为
AxiosError(ERR_BAD_OPTION_VALUE)抛出,调用方可以按标准 axios 错误流程处理。
URL 内嵌凭据的回退机制
除显式 auth 选项外,Node.js 的 http 适配器与 fetch 适配器还支持从请求 URL 中推断 Basic 凭据,例如:
https://myUser:myPassword@api.example.com/data
fetch 适配器中该逻辑位于 lib/adapters/fetch.js,与文档描述一致,且有两个值得注意的实现行为:
// HTTP basic authentication
let auth = undefined;
const configAuth = own('auth');
if (configAuth) {
// 显式 auth 优先
auth = { username, password };
}
if (maybeWithAuthCredentials(url)) {
const parsedURL = new URL(url, platform.origin);
// 仅当没有显式 auth 时,才从 URL 解析凭据
if (!auth && (parsedURL.username || parsedURL.password)) {
auth = {
username: decodeURIComponentSafe(parsedURL.username),
password: decodeURIComponentSafe(parsedURL.password),
};
}
// 无论凭据来自何处,最终都会从 URL 中剥离
if (parsedURL.username || parsedURL.password) {
parsedURL.username = '';
parsedURL.password = '';
url = parsedURL.href;
}
}
if (auth) {
headers.delete('authorization');
headers.set(
'Authorization',
'Basic ' + btoa(encodeUTF8((auth.username || '') + ':' + (auth.password || '')))
);
}
- 显式
auth选项优先:if (!auth && ...)保证 URL 内嵌凭据只是回退来源,auth选项始终覆盖 URL 中的用户名密码。这也是官方文档对新代码的明确建议:优先使用显式auth,避免凭据散落在 URL 字符串里(日志、document.referrer等途径更易泄漏)。 - 百分号编码会先解码:
decodeURIComponentSafe()(lib/adapters/fetch.js)会把 WHATWG URL 解析器返回的 percent-encoded 凭据解码,例如my%40email.com:pass会按my@email.com:pass发送;解码失败时回退原值而非抛错。 - URL 中的凭据会被剥离:最终实际发出的 URL 不含用户名密码,凭据只通过
Authorization头传输;同时会先headers.delete('authorization'),防止手动设置的Authorization头与 Basic 凭据冲突。
http 适配器(lib/adapters/http.js)实现等价逻辑:将 username:password 组合后通过 Node 的 auth 请求选项下发,同样先删 authorization 头再走 URL 回退。此外它还有一个细节——重定向时保留认证:lib/adapters/http.js 通过 beforeRedirects.auth 钩子在 3xx 跳转后恢复 auth,避免跨路径重定向导致 Basic 凭据丢失。
API Key:请求头或查询参数二选一
API Key 的传递方式由服务端约定决定,axios 侧只需选择对应的传递通道:
// 方式一:作为请求头(推荐,凭据不进入 URL)
const api = axios.create({
baseURL: "https://api.example.com",
headers: { "X-API-Key": "your-api-key-here" },
});
// 方式二:作为查询参数
const response = await axios.get("https://api.example.com/data", {
params: { apiKey: "your-api-key-here" },
});
两种方式的差异在于:
- 请求头方式在实例上配置一次即可全局生效,且凭据不会出现在 URL 中——URL 更容易被代理、CDN、浏览器历史与日志记录,因此除非 API 明确要求,应优先选择请求头;
- 查询参数方式利用
params自动做 URL 编码,适合老式 API 的约定,但注意 axios 不会对其做脱敏,落日志时会完整暴露。
Token 续期:响应拦截器 + 失败请求队列
当 access token 过期时,需要静默刷新并重试失败的请求。官方文档给出的完整实现是"响应拦截器 + 刷新锁 + 等待队列"模式,能避免并发请求同时触发多次刷新:
import axios from "axios";
const api = axios.create({ baseURL: "https://api.example.com" });
// 跟踪是否已有刷新请求在途,避免并发重复刷新
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 已有刷新在途:把当前请求挂起,等刷新完成后再重试
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then((token) => {
originalRequest.headers["Authorization"] = `Bearer ${token}`;
return api(originalRequest);
})
.catch((err) => Promise.reject(err));
}
originalRequest._retry = true;
isRefreshing = true;
try {
const { data } = await axios.post("/auth/refresh", {
refreshToken: localStorage.getItem("refresh_token"),
});
const newToken = data.access_token;
localStorage.setItem("access_token", newToken);
api.defaults.headers.common["Authorization"] = `Bearer ${newToken}`;
processQueue(null, newToken);
return api(originalRequest);
} catch (refreshError) {
processQueue(refreshError, null);
// 刷新失败:清理本地凭证,跳转登录或派发全局事件
localStorage.removeItem("access_token");
window.location.href = "/login";
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
}
);
该模式的三个关键点值得理解:
_retry标记防止死循环:同一请求 401 后只允许自动重试一次;如果刷新后的 token 仍然 401,错误会原样上抛,而不会无限循环。isRefreshing锁 +failedQueue队列:并发场景下只有第一个 401 请求真正发起刷新,其余 401 请求挂起为 Promise 进入队列;刷新成功时processQueue(null, newToken)统一放行并重放,刷新失败时统一 reject。这是处理"短时间大量请求同时过期"的标准方案。- 刷新请求走裸
axios而非api实例:避免刷新请求本身又进入该拦截器(虽然_retry也能兜底),同时刷新成功后通过api.defaults.headers.common更新后续请求的默认头,与请求拦截器配合形成完整闭环。
Cookie 会话认证:withCredentials 与 CORS 约束
对于基于服务端会话、依赖 Cookie 的 API,需要在实例上开启 withCredentials: true,让跨域请求携带 Cookie:
const api = axios.create({
baseURL: "https://api.example.com",
withCredentials: true, // 每次请求都携带 Cookie
});
需要注意服务端 CORS 的硬性约束:withCredentials: true 要求服务器响应 Access-Control-Allow-Credentials: true,且 Access-Control-Allow-Origin 必须是具体来源(不能使用 * 通配符),否则浏览器会直接拦截响应。
从源码看,该配置在不同适配器中有对应的落地实现:
- XHR 适配器(lib/adapters/xhr.js):配置存在时直接透传给 XMLHttpRequest——
request.withCredentials = !!_config.withCredentials; - fetch 适配器(lib/adapters/fetch.js):把布尔值映射为 fetch 的
credentials模式——true映射为'include'、false映射为'omit',未设置时默认为'same-origin'(lib/adapters/fetch.js),即默认只携带同源 Cookie,行为与浏览器 fetch 规范一致; - http 适配器(Node):Node 环境没有浏览器 Cookie 概念,跨域请求的 Cookie 通常依赖
http适配器内置的 Cookie 处理逻辑而非该选项,withCredentials主要针对浏览器端跨域场景。
另外,axios 对同源请求默认会自动携带 XSRF Token:resolveConfig 中(lib/helpers/resolveConfig.js)仅在标准浏览器环境且 withXSRFToken === true 或 URL 同源时,从 xsrfCookieName 指定的 Cookie 读取值并写入 xsrfHeaderName 请求头,用于服务端防跨站请求伪造校验。做 Cookie 会话认证时,可与服务端 CSRF 校验机制配合使用。
选型小结
- OAuth2 / JWT 前后端分离:请求拦截器注入 Bearer Token + 响应拦截器做 401 自动续期,是 axios 生态最完整的组合;
- 内网 / 服务间简单认证:用
auth选项,并注意"新代码优先显式auth而非 URL 内嵌凭据"的官方建议——显式选项优先级更高,且凭据不会残留在 URL 中; - 第三方开放平台:确认服务端约定后,用实例默认头或
params传递 API Key,优先请求头; - 同域会话 / SSO:
withCredentials: true,并确认服务端 CORS 头满足Access-Control-Allow-Credentials: true+ 具体 Origin 的要求。
以上所有配置项与行为均可在当前仓库源码中查证:核心编码逻辑见 lib/helpers/resolveConfig.js,各适配器的凭据处理见 lib/adapters/fetch.js、lib/adapters/http.js、lib/adapters/xhr.js,行为验证可参考 tests/browser/basicAuth.browser.test.js 与 tests/unit/helpers/resolveConfig.test.js。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00