JS-彻底告别跨域烦恼:从同源策略到 CORS 深度实战

92 阅读4分钟

前言

在 Web 开发中,“跨域”是每个前端开发者绕不开的坎。当你看到控制台报出 Access-Control-Allow-Origin 错误时,其实是浏览器的同源策略在起作用。本文将带你深度解析跨域的本质,并掌握主流的解决方案。

一、 什么是跨域?

1. 同源策略 (Same-origin policy)

跨域问题的根源是浏览器为了安全而实施的同源策略。所谓“同源”,是指两个 URL 的以下三部分完全相同:

  • 协议:http 、 https
  • 域名 :域名就是我们每次访问网站输入的网址,每个域名都对应了一个IP地址,浏览器会通过域名解析来获取这个IP地址,例如www.test.com
  • 端口号 :80 、 8080

2. 域名解析小科普

域名是 IP 地址的“外壳”。

  • 顶级域名:.com, .cn
  • 一级域名:test.com
  • 二级域名www.test.com
  • 注意:一级域名和二级域名之间、二级域名和三级域名之间,统统属于跨域!比如在www.test.com网页使用 XMLHttpRequest 请求time.test.con的页面内容,由于它们不是同一个源,所以就涉及到了跨域(在 A 站点中去访问不同源的 B 站点的内容)。默认情况下,跨域请求是不被允许的,你可以看下面的示例代码:

二、 解决方案一:JSONP

1. 实现原理

利用 <script> 标签的 src 属性不受同源策略限制的特性。通过动态创建 script 标签,发送一个带有 callback 参数的 GET 请求。

2. 代码实现

前端逻辑:

btn.click(() => {
  var script = document.createElement("script");// 创建 scrip 标签
  script.src = `http://localhost:3000?callback=show`;// 添加 src 请求路径
  document.body.appendChild(script);
  script.onload = function(){
    document.body.removeChild(script)
  }
});

//这个函数就是回调函数,它会拼接到src属性中,并对数据进行操作
function show(result) {
  // ...
console.log("获取到的数据:", result);
}

服务端逻辑:

const http = require("http")
const url = require("url")
http.createServer(
  (req,res)=>{
    var callback = url.parse(req.url,true).query.callback;
    var severData = "xxxxxxxx";
    severData = JSON.stringify(severData)
    res.writeHead(200,{
      "Content-Type": "text/plain;charset=utf-8"
    });
    res.write(`${callback}(${severData})`);
    res.end();
  }
).listen(80)

局限性仅支持 GET 请求,不安全,且无法处理复杂的报错信息。


三、 解决方案二:CORS (现代的标准方案)

CORS(跨域资源共享)是目前的标准解法。它将请求分为简单请求非简单请求

1. 简单请求

条件:方法为 GET/POST

  • 流程:浏览器直接发起请求,并在 Header 中带上 Origin
  • 服务端:通过返回 Access-Control-Allow-Origin 来告知浏览器是否放行。

2. 非简单请求(预检请求)

条件:包含 PUT/DELETE 方法。

  • 流程:浏览器会先发送一个 OPTIONS 方法的“预检请求”。

  • 关键字段

    • Access-Control-Max-Age: 设置预检请求的缓存时间(秒),避免每次请求都多发一次 OPTIONS,优化性能。

3. Nginx 服务端配置示例

server {
    listen 80;
    location / {
        # 允许跨域的域名,建议生产环境指定具体域名而非 *
        add_header 'Access-Control-Allow-Origin' '$http_origin';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
        
        # 处理预检请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            return 204;
        }
    }
}

四、 面试模拟题

Q1:为什么要有同源策略?如果没有会怎样?

参考回答:

同源策略主要是为了防止 CSRF(跨站请求伪造) 攻击。如果没有同源策略,黑客的网页可以随意读取你银行网页的 Cookie 或 DOM 内容,从而冒充你发送请求或窃取敏感信息。

Q2:CORS 预检请求(OPTIONS)在什么情况下会触发?

参考回答:

当请求满足以下任意条件时会触发预检:

  1. 使用了 PUTDELETECONNECTOPTIONSTRACEPATCH 方法。
  2. 设置了非简单的 Header 字段(如 Authorization、自定义 Token)。
  3. Content-Type 的值不属于 application/x-www-form-urlencodedmultipart/form-datatext/plain

Q3:如何解决跨域时 Cookie 无法携带的问题?

参考回答:

  1. 前端XMLHttpRequestfetch 需设置 withCredentials: true
  2. 服务端:设置响应头 Access-Control-Allow-Credentials: true
  3. 注意:当开启凭证携带时,Access-Control-Allow-Origin 不能设置为 * ,必须指定具体的域名。

五、 总结

方案原理优点缺点
JSONP<script> 标签不受限兼容性极好(老浏览器)只支持 GET,安全性差
CORS服务端 Header 授权正式标准,支持所有方法需服务端配合,有预检开销