同源策略

  • 协议
  • 域名
  • 端口

三个元素某个不同都属于跨域 

常见的解决方法

1.跨域资源共享(CORS)

1.1 安全请求

(1)方法:GET、POST或HEAD

(2)请求头(只包含以下请求头):

Accept、Accept-Language、Content-Language、Content-Type的值为application/x-www-form-urlencoded、multipart/form-data或text/plain

1.2 其他请求

1.2.1 预检请求

跨域请求~_html


- 浏览器自主发出,请求服务器访问资源许可

- options请求

跨域请求~_html_02


- 没有请求体

- 请求头:

Access-Control-Request-Method

Access-Control-Request-Headers

跨域请求~_html_03


服务器同意客户端请求,则响应头会携带以下:

跨域请求~_html_04


  • Access-Control-Allow-Origin 请求源为* 则是任意源头都可
  • Access-Control-Allow-Methods 允许的方法
  • Access-Control-Allow-Headers 允许的请求头
  • Access-Control-Max-Age 缓存此权限的秒数,这个时间范围内后续请求将不会触发预检

1.2.2  实际请求

预检请求通过后,实际请求响应头还是会加上Access-Control-Allow-Origin,然后客户端就可以获取到服务端资源了

跨域请求~_html_05


需要注意:

如果需要携带凭据(cookie或者其他)的话:

客户端请求需要携带credentials:"include":

fetch("https://xxx.com", {
  credentials: "include",
});


服务器响应头会返回Access-Control-Allow-Credentials:true :

跨域请求~_html_06


2. JSONP

动态创建script标签,并注入get请求实现跨域请求~

可看之前文章

3.使用代理服务器

1.前后端分离项目在开发阶段会在本地配置代理:

以下是常见打包工具配置:

(1)webpack

module.exports = {
    entry: '',
    output: {
        filename: '',
        path: path.resolve(__dirname, 'dist')
    },
    devServer: {
        ...

        port: 9090,
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: true,
                pathRewrite: { '^/api': '' }
            }
        }
    }
};


(2)vite

export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});


 2. 生产会使用nginx或者其他web服务器去配置代理

server {
    listen 80;
    server_name xxx;

    location / api / {
        proxy_pass http://localhost:3000/;
        proxy_set_header Host $host;
        proxy_set_header X- Real - IP $remote_addr;
        proxy_set_header X - Forwarded - For $proxy_add_x_forwarded_for;
        proxy_set_header X - Forwarded - Proto $scheme;

        # CORS Headers
        add_header Access - Control - Allow - Origin *;
        add_header Access - Control - Allow - Methods 'GET, POST, OPTIONS';
        add_header Access - Control - Allow - Headers 'DNT,User-Agent,X-Requested-                    
        With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header Access - Control - Expose - Headers 'Content-Length,Content-Range';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

location / {
    root /var/www/html;
        index index.html;
    }
}


4.iFrame和PostMessage (一般不会用,除非特殊场景

主页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>example</title>
</head>
<body>
    <h1>example</h1>
    <div id="message"></div>
    <iframe id="frame" src="http://xx/yy" style="display:none;"></iframe>

    <script>
        window.onload = function(){
            const iframe = document.getElementById('frame');

        window.addEventListener('message', (event) => {
            if (event.origin !== 'http://xx') return;
            const data = event.data;
            document.getElementById('message').innerText = data.message;
        });

        function fetchData() {
            iframe.contentWindow.postMessage('queryData', 'http://xx');
        }

        fetchData();
        }
    </script>
</body>
</html>


iframe 内部页面 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iframe</title>
</head>
<body>
    <script>
        window.addEventListener('message', (event) => {
            if (event.data === 'queryData') {
                fetch('http://yy')
                    .then(response => response.json())
                    .then(data => {
                        event.source.postMessage(data, event.origin);
                    });
            }
        });
    </script>
</body>
</html>