为什么我的 Auth Token 藏在了 Network 面板的 Doc 里?

最近在调试一个第三方登录功能时,我遇到了一个奇怪的现象:明明看到页面跳转了,用户也成功登录了,但我在 Network 面板里怎么都找不到那个关键的 auth code。习惯性地勾选了 Fetch/XHR 过滤器,翻遍了所有请求,就是没有🤔。

直到我取消过滤,切换到 Doc 类型,才在 URL 参数里看到了 ?code=abc123&state=xyz

这次经历让我开始思考:Network 面板里的 Doc 到底是什么?为什么有些认证信息会出现在这里而不是 Fetch/XHR 里?这背后有什么机制?

问题的起源

一次真实的调试经历

场景是这样的:我在接入 GitHub OAuth 登录,流程看起来很顺利------用户点击"使用 GitHub 登录",跳转到 GitHub 授权页面,授权后跳回我的应用。但问题来了,我需要在回调中获取 GitHub 返回的 authorization code,然后用这个 code 去换取 access token。

按照惯例,我打开 Chrome DevTools,勾选 Network 面板的 Fetch/XHR 过滤器,准备查看 API 请求。结果------什么都没有。没有看到任何包含 code 参数的请求。

困惑了好一会儿,我才想起取消过滤,查看所有类型的请求。这时我注意到有个 Type 为 document 的请求,点开一看,Request URL 是:

ini 复制代码
https://myapp.com/callback?code=gho_xxxxxxxxxxxx&state=random_string

原来 code 一直在这里!只是它不是通过 Fetch/XHR 发送的,而是作为页面跳转(重定向)的一部分。

这背后的几个疑问

这次经历让我产生了几个问题:

  1. Network 面板里的 Doc 是什么?和 Fetch/XHR 有什么区别?
  2. 为什么 OAuth 的认证信息会出现在 Doc 请求里?
  3. 什么时候我应该去查看 Doc 类型的请求?
  4. Network 面板里其他的过滤类型都代表什么?

接下来,让我们一个个搞清楚。

认识 Network 面板中的请求分类

什么是 Doc 请求?

Doc(Document)请求,指的是 HTML 文档请求,也就是浏览器加载的页面本身。

这个定义听起来有点抽象,我们用代码来看什么情况下会产生 Doc 请求:

javascript 复制代码
// 环境: 浏览器
// 场景: 各种触发 Doc 请求的方式

// 1. 直接在地址栏输入 URL
// https://example.com
// → Type: document

// 2. 点击链接跳转
const link = document.createElement('a');
link.href = '/dashboard';
link.click();
// → Type:document

// 3. JavaScript 页面跳转
window.location.href = '/dashboard';
// → Type:document

// 4. 表单提交(非 AJAX)
const form = document.createElement('form');
form.action = '/login';
form.method = 'POST';
form.submit();
// → Type:document

// 5. 服务端 HTTP 重定向
// Server Response:
// HTTP/1.1 302 Found
// Location:/callback?code=xxx
// → 浏览器自动发起新的 document 请求

相对的,下面这些操作不会产生 Doc 请求:

javascript 复制代码
// 环境: 浏览器
// 场景: 这些是 Fetch/XHR 请求,不是 Doc

// 使用 fetch API
fetch('/api/user')
  .then(res => res.json())
// → Type: fetch

// 使用 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.send();
// → Type: xhr

// 使用 axios 等库
axios.get('/api/posts');
// → Type: xhr

Doc 请求的关键特征:

  • ✅ 会触发页面导航(浏览器地址栏的 URL 会改变)
  • ✅ 浏览器会解析返回的 HTML 并渲染页面
  • ✅ 可能伴随着 Cookie 的设置和自动携带
  • ✅ 会刷新整个页面(除非是 iframe)

Network 面板的过滤类型全解

为了更清楚地理解 Doc 在整个 Network 面板中的位置,我整理了一个对比表:

类型 含义 何时查看 典型例子
All 所有请求 排查复杂问题,需要看完整链路 -
Doc HTML 文档 页面跳转、重定向、认证回调 登录重定向、OAuth 回调
Fetch/XHR AJAX 请求 API 调用、异步数据加载 fetch('/api/user')
JS JavaScript 文件 脚本加载问题、404 错误 <script src="app.js">
CSS 样式表 样式未生效、加载失败 <link href="style.css">
Img 图片 图片显示异常、加载慢 <img src="photo.jpg">
Media 音视频 媒体播放问题 <video src="movie.mp4">
Font 字体文件 字体显示异常、图标不显示 @font-face 引用的字体
WS WebSocket 实时通信连接问题 new WebSocket(url)

一个简单的记忆方法:

  • Doc = 页面本身(会导致页面刷新或跳转)
  • Fetch/XHR = 页面背后的数据请求(页面不刷新)
  • 其他类型 = 页面加载需要的各种资源

什么时候需要查看 Doc?

根据我的经验,以下场景必须查看 Doc 类型的请求:

1. 调试页面跳转流程

用户登录 → 重定向到首页 → 重定向到之前访问的页面

每一次重定向都是一个 Doc 请求,需要追踪完整链路。

2. 排查认证问题

  • OAuth/SAML 等第三方登录的回调
  • Cookie 是否正确设置(查看 Response Headers 的 Set-Cookie)
  • URL 参数中的临时 token 或 code

3. 追踪 HTTP 重定向链路

  • 服务端返回 301/302/303/307/308 状态码
  • 需要查看 Location 头部,了解重定向目标
  • 排查重定向循环问题

4. 分析页面加载性能

  • 首次加载时间(TTFB、DOM 解析时间)
  • 服务端渲染(SSR)的响应时间

调试技巧:

在 Chrome DevTools 的 Network 面板中:

  1. ✅ 勾选 "Preserve log"(保留日志)

    • 页面跳转后不会清空请求记录
    • 可以看到完整的重定向链路
  2. ✅ 取消过滤或选择 "All"

    • 看到所有类型的请求
    • 不会漏掉关键信息
  3. ✅ 关注 Status 列的 3xx 状态码

    • 302 Found, 301 Moved Permanently 等
    • 这些都是重定向请求

为什么需要 Doc 分类?

浏览器的两种数据获取方式

理解 Doc 和 Fetch/XHR 的区别,关键在于理解浏览器获取数据的两种不同方式。

方式 1:页面导航(Doc 请求)

这是浏览器的原生机制,从 Web 诞生之初就存在:

html 复制代码
<!-- 环境: 浏览器 -->
<!-- 场景: 传统的页面导航 -->

<!-- 点击链接 -->
<a href="/products">查看产品</a>

<!-- 表单提交 -->
<form action="/login" method="POST">
  <input type="text" name="username">
  <input type="password" name="password">
  <button type="submit">登录</button>
</form>

特点:

  • ✅ 浏览器自动处理 Cookie、缓存、重定向
  • ✅ 不需要写 JavaScript 代码
  • ✅ 天然支持跨域(不受 CORS 限制)
  • ❌ 会刷新整个页面,用户体验较差

方式 2:异步请求(Fetch/XHR 请求)

这是 AJAX 时代引入的技术,由 JavaScript 控制:

javascript 复制代码
// 环境: 浏览器
// 场景: 现代单页应用的数据获取

// 使用 fetch API
async function login(username, password) {
  const response = await fetch('/api/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ username, password })
  });
  
  const data = await response.json();
  return data.token;
}

// 后续请求手动携带 token
async function getUserInfo(token) {
  const response = await fetch('/api/user', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
}

特点:

  • ✅ 无需刷新页面,用户体验好
  • ✅ 完全由 JavaScript 控制,灵活性高
  • ❌ 需要手动处理认证信息(token)
  • ❌ 受 CORS 限制,跨域需要服务端配合

为什么要区分这两种方式?

因为它们的调试方法安全模型适用场景都不同:

特性 Doc(页面导航) Fetch/XHR(异步请求)
触发方式 链接点击、表单提交、重定向 JavaScript 代码
页面刷新
Cookie 携带 自动 需配置 credentials
跨域限制 无(但有其他安全机制) 严格的 CORS 检查
调试位置 Network → Doc Network → Fetch/XHR
适用场景 传统 Web、SSO、OAuth 单页应用、REST API

真实案例:OAuth 认证为什么用 Doc?

现在让我们回到文章开头的问题:为什么 OAuth 的 auth code 会出现在 Doc 请求里?

让我用一个完整的 OAuth 流程来说明:

sequenceDiagram participant User as 用户浏览器 participant App as 你的应用 participant GitHub as GitHub User->>App: 1. 点击"Login with GitHub" Note over User,App: Fetch/XHR: GET /api/auth/github App->>User: 2. 返回授权 URL User->>GitHub: 3. 重定向到 GitHub Note over User,GitHub: Doc 请求: 页面跳转 GitHub->>User: 4. 返回登录页面 HTML User->>GitHub: 5. 用户输入账号密码 GitHub->>User: 6. 302 重定向回你的应用 Note over User,App: Doc 请求: HTTP 重定向 User->>App: 7. GET /callback?code=xxx&state=yyy Note over User,App: ⭐ code 在这个 Doc 请求的 URL 里 App->>GitHub: 8. 用 code 换 token(服务端) Note over App,GitHub: 这个请求不在浏览器里 GitHub->>App: 9. 返回 access_token

关键时刻分解:

步骤 3:跳转到 GitHub(Doc 请求)

javascript 复制代码
// 环境: 浏览器
// 场景: 用户点击登录按钮后

// 前端构造 GitHub 授权 URL
const authUrl = 'https://github.com/login/oauth/authorize?' + 
  'client_id=your_client_id&' +
  'redirect_uri=https://yourapp.com/callback&' +
  'state=random_string';

// 页面跳转(产生 Doc 请求)
window.location.href = authUrl;

// Network 面板会看到:
// Type: document
// URL: https://github.com/login/oauth/authorize?...
// Status: 200

步骤 6-7:GitHub 重定向回你的应用(Doc 请求)

javascript 复制代码
# GitHub 服务器的响应
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=gho_xxxx&state=random_string

# 浏览器自动发起新的请求
GET /callback?code=gho_xxxx&state=random_string HTTP/1.1
Host: yourapp.com

# Network 面板会看到:
# Type: document
# URL: https://yourapp.com/callback?code=gho_xxxx&state=random_string
# Status: 200

这就是你在 Doc 里找到 auth code 的原因!

为什么 OAuth 必须用重定向(Doc)?

可能你会想:为什么不用 Fetch/XHR 来实现 OAuth?这样就不用刷新页面了。

让我解释一下为什么这样做不通:

原因 1:安全性------用户密码不能经过第三方应用

问题情境: 用户(你)想让你的应用(比如 Notion)访问你的 Google Drive

❌ 错误做法:

Notion 让你在 Notion 页面输入 Google 密码

→ Notion 拿到了你的 Google 密码

→ 风险:Notion 可以随意访问你的 Google 账户

✅ 正确做法(OAuth):

Notion 把你重定向到 Google 登录页面

→ 你直接在 Google 页面输入密码

→ Notion 永远拿不到你的密码

→ Google 只给 Notion 一个有限权限的 token

重定向保证了用户直接在资源提供方(Google)的页面输入密码,密码不会经过第三方应用。

原因 2:跨域限制------CORS 会阻止 AJAX 请求

javascript 复制代码
// 环境: 浏览器
// 场景: 如果尝试用 AJAX 请求 GitHub 授权页面

// ❌ 这样做不行
fetch('https://github.com/login/oauth/authorize?...')
  .then(res => res.text())
  .then(html => {
    // 想法:拿到 GitHub 登录页面的 HTML,显示在我的页面里
  });

// 会遇到的问题:
// 1. CORS 错误:GitHub 不允许你的域名跨域请求
// 2. 即使能拿到 HTML,用户在你的页面输入密码也不安全
// 3. 无法获取 GitHub 的 Cookie,登录状态无法维护

原因 3:浏览器的自动行为------重定向无需编写代码

javascript 复制代码
# HTTP 重定向是浏览器的标准功能
# 服务器只需要返回一个响应头:

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=xxx

# 浏览器会自动:
# 1. 提取 Location 头的 URL
# 2. 发起新的请求(Doc 请求)
# 3. 更新地址栏 URL
# 4. 渲染新页面

# 不需要写任何 JavaScript!

除了 OAuth,传统的 Cookie 认证也主要依赖 Doc 请求。让我们看一个例子:

javascript 复制代码
// 环境: 浏览器
// 场景: 传统的表单登录

// HTML 表单
/*
<form action="/login" method="POST">
  <input type="text" name="username">
  <input type="password" name="password">
  <button type="submit">登录</button>
</form>
*/

// 用户提交表单时发生的事情:

// 1. 浏览器发送 Doc 请求
// POST /login HTTP/1.1
// Content-Type: application/x-www-form-urlencoded
// 
// username=alice&password=secret123

// 2. 服务器验证成功,返回重定向 + Set-Cookie
// HTTP/1.1 302 Found
// Set-Cookie: session_id=abc123xyz; HttpOnly; Secure; SameSite=Strict
// Location: /dashboard

// 3. 浏览器自动:
//    - 保存 Cookie
//    - 发起新的 Doc 请求到 /dashboard
//    - 自动携带 Cookie

// GET /dashboard HTTP/1.1
// Cookie: session_id=abc123xyz

为什么是 Doc 请求?

  1. Set-Cookie 在响应头中 → 只有 Doc 请求会自动处理 Set-Cookie
  2. Cookie 自动携带 → Doc 请求会自动带上同域的 Cookie
  3. 表单提交 → 传统 HTML form 产生的就是 Doc 请求

对比现代方式(Token 认证) :

javascript 复制代码
// 环境: 浏览器
// 场景: 现代单页应用的 Token 认证

// 1. 用户登录(Fetch/XHR 请求)
async function login(username, password) {
  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  
  const data = await response.json();
  // { access_token: "eyJhbG...", refresh_token: "..." }
  
  // 手动存储 token
  localStorage.setItem('access_token', data.access_token);
}

// 2. 后续请求手动携带 token(Fetch/XHR 请求)
async function getUserInfo() {
  const token = localStorage.getItem('access_token');
  
  const response = await fetch('/api/user', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
}

两种方式对比:

特性 Cookie + Doc Token + Fetch/XHR
请求类型 Doc(页面跳转) Fetch/XHR(异步)
认证信息位置 Cookie(Response Headers) JSON 响应体
携带方式 浏览器自动 手动添加到 Headers
用户体验 页面刷新 无刷新,流畅
调试位置 Doc 请求 Fetch/XHR 请求
典型应用 传统 Web 应用、企业内部系统 单页应用、移动 App API

实战调试技巧

案例:找不到 OAuth 的 auth code

让我用实际的调试步骤演示一遍:

问题场景:

  • 接入 GitHub OAuth 登录
  • 用户点击登录,跳转到 GitHub,授权后跳回应用
  • 需要获取 URL 中的 code 参数

错误的调试方法:

  1. 打开 DevTools → Network 面板
  2. 勾选 Fetch/XHR 过滤器
  3. 刷新页面,点击登录
  4. 找不到包含 code 参数的请求 ❌

正确的调试步骤:

✅ Step 1: 取消所有过滤,选择 "All" → 看到所有类型的请求

✅ Step 2: 勾选 "Preserve log"(保留日志) → 防止页面跳转后记录被清空

✅ Step 3: 点击登录,完成授权流程

✅ Step 4: 在 Network 面板中,找 Type 为 "document" 的请求 → 按时间顺序,找最后几个 Doc 请求

✅ Step 5: 点击 Doc 请求,查看详情 → Request URL: yourapp.com/callback?co... → code 就在这里!

✅ Step 6: 查看 Headers 标签

→ Request Headers 可以看到 Cookie、Referer

→ Response Headers 可以看到 Set-Cookie

在 Console 中提取 code:

javascript 复制代码
// 环境: 浏览器 Console
// 场景: 在回调页面提取 URL 参数

// 方法 1: 使用 URLSearchParams
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');

console.log('Auth code:', code);
console.log('State:', state);

// 方法 2: 手动解析(不推荐,但可以理解原理)
const queryString = window.location.search; // "?code=xxx&state=yyy"
const pairs = queryString.substring(1).split('&');

const result = {};
pairs.forEach(pair => {
  const [key, value] = pair.split('=');
  result[key] = decodeURIComponent(value);
});

console.log(result);
// { code: "gho_xxxx", state: "yyyy" }

调试清单

当你需要排查认证或重定向问题时,参考这个清单:

查看 Doc 请求时,重点关注:

位置 关键信息 用途
Request URL URL 参数 ?code=xxx&state=yyy OAuth code、查询参数
Status 3xx 状态码(301/302/303) 识别重定向链路
Request Headers Cookie 字段 检查认证信息是否携带
Response Headers Set-Cookie 字段 检查 Cookie 是否正确设置
Response Headers Location 字段 查看重定向目标地址

常见问题排查:

  1. ❓ 为什么看不到某些请求?

    • → 检查是否勾选了 "Preserve log"
    • → 取消类型过滤,选择 "All"
  2. ❓ 为什么 Cookie 没有携带?

    • → 检查 Cookie 的 Domain 是否匹配
    • → 检查 Cookie 的 Path 是否正确
    • → 检查 SameSite 属性(Strict/Lax/None)
  3. ❓ 为什么一直重定向循环?

    • → 启用 "Preserve log",查看完整的重定向链
    • → 找出循环的起点和终点
    • → 检查服务端的重定向逻辑
  4. ❓ 为什么 OAuth code 参数没有?

    • → 检查 state 参数是否匹配(CSRF 防护)
    • → 查看 GitHub 的错误提示(可能在 URL 参数里)
    • → 确认 redirect_uri 配置是否正确

Chrome DevTools 实用技巧

1. 复制请求为 cURL

bash 复制代码
# 在 Network 面板中:
# 1. 右键请求
# 2. Copy → Copy as cURL

# 得到类似这样的命令:
curl 'https://yourapp.com/callback?code=gho_xxxx&state=yyyy' \
  -H 'Accept: text/html' \
  -H 'Cookie: session_id=abc123' \
  -H 'Referer: https://github.com/login'

# 可以在终端中重放这个请求

2. 保存网络日志(HAR 文件)

diff 复制代码
右键 Network 面板 → Save all as HAR with content

用途:
- 保存完整的请求/响应记录
- 可以导入到其他工具分析
- 分享给同事协助调试

⚠️ 注意:HAR 文件包含敏感信息(Cookie、Token),不要随意分享

3. 过滤和搜索

makefile 复制代码
在 Network 面板的 Filter 输入框中:

# 按域名过滤
domain:github.com

# 按状态码过滤
status-code:302

# 按请求方法过滤
method:POST

# 按资源大小过滤
larger-than:1M

# 组合使用
domain:api.example.com status-code:200

小结

通过这次对 Network 面板 Doc 类型的探索,我理清了几个关键点:

核心要点

1. Doc 是什么?

  • HTML 文档请求,即浏览器加载的页面本身
  • 所有触发页面导航的操作(链接点击、表单提交、重定向)都会产生 Doc 请求
  • 会刷新页面,会自动处理 Cookie 和重定向

2. 什么时候需要看 Doc?

  • 调试页面跳转重定向流程
  • 排查认证问题(OAuth 回调、Cookie 设置)
  • 追踪 URL 参数中的临时信息(code、state、token)
  • 分析完整的请求链路(配合 Preserve log)

3. 为什么需要 Doc 分类?

  • 浏览器有两种数据获取方式:页面导航(Doc)和异步请求(Fetch/XHR)
  • OAuth/SSO 等认证流程必须用重定向(安全性 + 跨域限制)
  • Cookie 认证依赖浏览器的自动行为(自动携带、自动处理 Set-Cookie)
  • 不同的机制需要不同的调试方法

4. Network 分类速查

  • Doc → 页面跳转、重定向、认证回调
  • Fetch/XHR → API 调用、异步数据加载
  • JS/CSS/Img → 页面资源加载
  • WS → WebSocket 实时通信

一个类比帮助记忆

把 Network 面板想象成一个物流追踪系统:

  • Doc = 你本人去取件(需要走到快递点,拿到包裹后回家)
  • Fetch/XHR = 快递送上门(你在家里,东西直接送到)
  • JS/CSS/Img = 包裹里的物品(笔记本、衣服、配件等)

当你需要亲自去某个地方完成某件事(比如银行签字、OAuth 授权),就是 Doc 请求。当你只需要获取数据(API 调用),就是 Fetch/XHR 请求。

一个调试口诀

看不到数据?先别慌

取消过滤查 All 类型

重定向认证看 Doc

API 数据看 XHR

保留日志 Preserve log

完整链路不会漏

参考资料

相关推荐
充钱大佬2 小时前
Python测试基础教程
python·log4j·apache
不听话坏2 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
likeyi072 小时前
require 和 import的区别
开发语言·前端
pany3 小时前
做 AI 友好的开源 Vue3 模板 🌈
前端·vue.js·ai编程
小二·3 小时前
React 19 + Next.js 15 现代前端开发实战:App Router / Server Components / 流式渲染
前端·javascript·react.js
初心丨哈士奇4 小时前
Python 四大基础容器|列表篇
python
谙忆10244 小时前
前端图片直传对象存储:OSS/S3 预签名 URL、STS 临时凭证与回调校验
前端
明理的信封5 小时前
AI 基础设施的“去 Python 化“:Rust 与 C# 的两条替代路径
人工智能·python·rust
CHNE_TAO_EMSM5 小时前
Android studio 打开文件时自动下载源码
前端·javascript·android studio
一孤程6 小时前
Airtest自动化测试第五篇:小程序与Web测试——跨平台自动化全覆盖
前端·自动化测试·小程序·自动化·测试·airtest