文章

同源和跨域的那些事

同源和跨域的那些事

同源和跨域的那些事

同源和跨域的那些事

01、为什么要跨域?

跨域的根本原因是浏览器的“同源策略”,得先了解什么是同源?—— 就是【协议+域名+端口号】相同,即为同源,只能向同源的服务发起AJAX请求。

1683359309870-68a2340b-93ee-4218-9261-43cb4857885c.jpeg

源1源2是否同源
a.comb.com🚫不同源,域名不同
http://a.comhttps://a.com🚫不同源,协议不同
a.com:80a.com:443🚫不同源,端口不同
gg.coma.gg.com🚫不同源,子域名不同
a.com/ssa.com/s2同源

:::info 可通过 location.originwindow.origin获取当前文档的源

:::

**❓**为什么要同源呢?

这是浏览器故意设计的,是浏览器的基本安全策略,否则会很容易受到XSS、CSRF攻击。只能向同源的服务发起AJAX请求,不可跨域请求,会被浏览器拦截。

**❓**有哪些限制规则呢?

  • ✅ 访问其他源的图片、CSS、JS是可以的,允许1683360070327-e9e5fcb4-cb2a-4a5c-a904-88b0e34ad606.jpeg

    02、如何实现跨域?

    随着互联网越来越复杂,需求也越来越多,跨域请求就很常见了。我们知道了跨域是浏览器的同源限制,就可以针对性的想办法了。

    2.1、JSONP跨域

    这是一种传统的跨域请求办法,借助于<script>标签元素,因为<script>src可以访问任何站点的资源。当然这需要服务端对应接口支持JSONP(JSON with padding)协议,所以是需要双方约定好,所以浏览器认为这是安全的。

    • 优点是兼任IE,实现跨域。
    • 缺点是不能控制请求过程,仅支持GET方式请求。因为只是一个<script>标签,浏览器自动发起的资源请求。

    :::warning JSONP(JSON with Padding)是JSON的一种”使用模式“,是一种非官方的协议,用于解决浏览器的跨域数据访问的问题。

    :::

    📢前端具体实现过程:

    1. 申明一个全局的回调函数“getData”来接收数据。
    2. 动态创建一个<script>标签,src为要跨域的API地址,URL中带上回调参数“callback=getData”。
    3. 服务端收到请求后,动态生成一个脚本,脚本内容是一个字符串,由回调+返回的数据构成:“getData('data')”。
    4. 本地执行远程脚本,回调函数“getData”运行,就得到了想要的数据。
    1
    2
    3
    4
    5
    6
    
    <script src="http:www.thrid.com/cors/api?q=key&callback=back"></script>
    <script>
      function back(data) {
        console.log(data);
      }
    </script>
    

    JSONP的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    function jsonp(url, args, cbName) {
      return new Promise((resolve, reject) => {
        const ele = document.createElement('script');
        window[cbName] = (data) => {
          resolve(data);
          document.body.removeChild(ele);
        }
        args = { ...args, callback: cbName };
        ele.src = `${url}?${Object.keys(args).map(k => `${k}=${args[k]}`).join('&')}`;
        document.body.appendChild(ele);
      });
    }
    //使用,api为360的公开接口
    jsonp('https://sug.so.360.cn/suggest', { format: 'jsonp', word: 'china' }, 'search')
      .then(function (data) {
        console.log(data)
      });
    

    2.2、CORS跨域

    CORS是什么?—— 跨域资源共享(cross-origin resource sharing),让AJAX可以跨域访问数据。这是为了满足跨域请求的需求,W3C新增加的特性,需要服务端的支持,不支持IE8/9。根据请求方式,浏览器将CORS分为两种情况:

    • 简单请求(安全请求):只支持GET、POST、HEAD,Header只支持部分字段。
    • 复杂请求(其他请求):简单请求以外的其他跨域请求。

    🔵简单请求

    基本原理就是在请求头加入一个身份来源标识,服务端根据这个标识来判等是否允许访问,如果允许则给一个允许的标记并返回响应。

    • 只支持GET、POST、HEAD。
    • header —— 我们仅能设置基础的安全字段:
      Accept
      Accept-Language
      Content-Language
      Content-Type 的值为 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。
      📢具体过程比较简单,前端只要在Header加入“Origin”即可:
    • 请求头Header加入要跨域的源:origin:http://www.main.com
    1
    2
    3
    4
    5
    6
    7
    
    GET /api HTTP/1.1
    Origin: http://www.main.com				//本次请求来自哪个源
    Host: http://www.third.com				//请求的第三方API
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0
    ...
    
    • 服务端收到请求后检查Origin,如果同意请求则正常响应,同时在响应的Header中加入特殊的“Access-Control-Allow-Origin”字段,申明支持的源,也可以用“*”表示支持任何源访问。
    • 浏览器收到响应后会检查“Access-Control-Allow-Origin”,和当前源对比,如果不合法则会报错——跨域。
    1
    2
    3
    4
    
    Access-Control-Allow-Origin: http://www.main.com		//请求允许的源
    Access-Control-Allow-Credentials: true							//是否允许cookie,cors默认不发送cookie,如果要发送,还需AJAX中设置withCredentials
    Access-Control-Expose-Headers: Content-Length,API-Key	//如果客户端想要访问其他非安全字段,则需要服务端明确定义哪些Header字段暴露出来
    Content-Type: text/html; charset=utf-8
    

    1681044883259-eac2c961-53a7-4cbc-9148-cfc6e51d5b07.png

    ###

    🟠复杂请求 不是简单请求的都称为复杂请求(非简单请求),如请求方法是PUT、DELETE,或Content-Type=application/json。相比于简单请求,复杂请求多了一次预请求。

    预请求:

    • 正式发送请求前,浏览器会自动发送一个预请求,问问服务端是否允许本次请求,如果回应允许才正式发送请求,后面就和简单请求相同了。
    • 预请求及其响应都没有body,采用OPTIONS方法。

    1683360667325-c0ae4cfa-b818-47c3-9a98-df1a22765536.png


    03、跨域小结

    因为同源是浏览器的限制,跨域的方法无非就是绕过,或采用CORS。

    跨域方案基本原理是否需要服务端支持
    JSONP借助🟠需要服务端支持JSONP协议
    CORSW3C标准支持的跨域方式,请求头添加Origin字段🟠需要服务端支持
    WebSocketWebSocket可以实现浏览器与服务端的双向通信,没有跨域的困惑。推荐第三方库 Socket.io,可以很方便的建立与服务端的Socket通信。🟠需要服务端支持,支持WebSocke
    iframe+postMessage使用window.postMessage()来实现窗口之间的通信🔵不需服务端处理,客户端绕过
    服务端代理由自己的同源服务端代理第三方的请求🟠需要服务端支持,代理请求
    nginx反向代理原理和服务端代理一样,用nginx配置一个代理服务🔵不需要服务端修改代码,需nginx支持
本文由作者按照 CC BY 4.0 进行授权