前端碰到跨域问题,你一般是怎么解决的?...

2,038 阅读3分钟

今天来写一下老生常谈的话题——跨域!

去搜索引擎搜跨域的文章,简直是一抓一大把。但是,跨域问题确实无论是在面试中还是项目开发中,我们几乎无论做到哪个层面的码农,只要涉及到“请求”、“前后端交互”等等这些方面,都避不开跨域。既然躲不掉,那就直接面对,把跨域“咬碎嚼烂”。

什么是同源策略❓︎

同源策略:协议+域名+端口,是三个有任何一个不同就会造成跨域。

协议

  • 不同协议: 一个http 一个https, 同一域名不同协议,不允许通信❌(http默认端口是80、https默认端口是443)

域名:

  • 不同域名: 一个 http://www.domain1.com、一个 http://www.domain2.com,不允许通信❌

  • 不同路径: 一个 http://www.domain.com/a.js、一个 http://www.domain.com/js/b.js, 不允许通信❌

  • 域名和域名对应的ip: 一个http://www.domain.com、一个 http://192.168.0.1

  • 子域不同: 一个http://a.domain.com、一个http://b.domain.com

端口:

  • 不同端口: 一个 http://www.domain.com、一个 http://www.domain.com:8080, 不允许通信❌

同源策略能帮助我们隔离恶意文档,减少可能被攻击的媒介。


跨域解决方案

JSONP

JSONP(JSON with Padding) 利用<script>标签的跨域特性来实现跨域请求。

原理

  • 客户端(网页)通过动态创建一个<script>标签,并设置其src属性为目标服务器的URL,并在URL中包含一个回调函数的名称作为参数。
  • 服务器收到请求后,根据请求的参数解析出回调函数的名称,并将需要传递的数据包裹在该回调函数中。
  • 服务器返回的数据被包装在一个js函数调用中,比如: handleResponse({ name: 'John', age: 30 })
  • 浏览器加载<script>标签时,会向目标服务器发起请求,获取返回的js代码。
  • 由于返回的js代码是全局作用域下执行,因此回调函数会被调用,并将返回的数据作为参数传递给回调函数。
  • 客户端定义的回调函数被触发,可以在回调函数中处理返回的数据

缺点

  • 只能get请求

JSONP只适用GET请求,因为<script>标签的加载只能通过get方法实现。

另外,由于JSONP是通过将数据包裹在函数调用中返回的。因此,服务器需要提前知道客户端定义的回调函数的名称,双方进行约定。

  • 有风险

jsonp原理相对简单,但存在安全风险。如果服务器返回的脚本包含一些恶意代码,可能会对客户端产生安全威胁。所以说(1、要保证服务服务器是可信的,2、是要对返回的数据进行安全验证和处理)。


客户端

<script>
  // 定义回调函数来处理返回的数据
  function handleResponse(data) {
    console.log(data.name); // 输出:John
    console.log(data.age); // 输出:30
  }

  // 构造动态创建的<script>标签
  var script = document.createElement("script");
  script.src = "http://localhost:5000/jsonp?callback=handleResponse";
  document.body.appendChild(script);
</script>

image.png

请求过去的参数:

image.png

服务端(nestjs): 返回和前端约定好的函数,并且包裹了需要传递的数据进去,返回给前端。前端得到后再进行处理。即可实现跨域。

image.png

返回的是:

image.png

// 定义回调函数来处理返回的数据
function handleResponse(data) {
    console.log(data.name); // 输出:John
    console.log(data.age); // 输出:30
}

控制台得到的结果:

image.png

以上这个例子就是jsonp处理跨域的一个例子。前端的地址是(http://127.0.0.1:5500/jsonp.html),后端的地址是(http://localhost:5000/jsonp)。根据此上(什么是同源策略)分析,不同端口是会跨域的,我们这里通过jsonp通过get接口处理了这么一个跨域的问题。


后端框架代码解决跨域

cors(cross-origin resource sharing(跨域资源共享))

:允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

说白了,cors 就是可以通过在服务端配置Access-Control-Allow-Origin: * 以及其他的一些参数。这么做的目的就是,由服务端告诉浏览器,只有满足这些条件你才能够请求我,否则就出现跨域限制,这就是做这个东西的目的,也就是安全.

thinkphp配置跨域:

在thinkphp框架中,使用Header类来设置跨域问题:

在控制器方法中添加以下代码:

header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept")

nestjs配置跨域:

在根目录的main.ts加上一句:

app.enableCors() // 启用跨域请求

image.png

上面这种既是任何域都能请求,实际开发中需要更详细地去控制:

app.enableCors({
    origin: 'http://example.com',
    methods: 'GET,PUT,POST, DELETE',
    allowedHeaders: ['Content-Type', 'Authorization'],
    // ...
})

cors配置详细文档

...

其他的框架就具体搜索。

Nginx跨域配置

server {
    listen 80;
    server_name 'xxx';
    
    location / {
        add_header 'Access-Control-Allow-Origin' '*'; // * 全部;或者配置具体的域 http://xxx (最重要的就是这一句)
        add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
    }
}

Apache跨域配置

#
<VirtualHost *:80>
    ServerName localhost
    DocumnetRoot "路径"
    <Directory "路径">
        //...
        
        // 这里添加这一句
        Header set Access-Control-Allow-Origin * // * 全部,可以指定某个域
    </Directory>
</VirtualHost>

这里讲一下

总结

单纯前端除了jsonp,和在前端本地开发时proxy这种方案(这个proxy只在开发时用,上线还是要搞一下服务端的以上解决,要么Nginx什么配置、要么后端代码加cors)。也就是说,浏览器跨域限制的目的,就是为了不让你在前端解决这个问题,如果前端都能解决跨域了,什么人都可以去看某个服务端的代码,比如说哔哩哔哩的接口,如果他们那边不告侵权,我们随随便便可以用别人的接口,做一个不同的东西上线,用别人的资源来给自己造福,哪里来的安全性。全剧终!

☎️ 希望对大家有所帮助,如有错误,望不吝赐教,欢迎评论区留言互相学习。