前端面试-网络-跨域相关

88 阅读5分钟

一、同源策略

同源策略是一种安全的策略模式,在浏览器中,只有在协议、主机、端口保持一致时,才能进行资源访问。
同源策略发生时,实际上网络请求已经发出去了,且服务器也响应了,但是被浏览器拦截了。
同源策略的制定是为了解决

  • DOM层面
    • DOM访问限制:跨域请求无法直接访问其他源的DOM(Document Object Model),包括读取和修改其他源网页的DOM元素。这样可以保护用户在其他域名下的网页中输入的敏感信息。
  • 网络层面
    • XMLHTTPRequest、fetch等网络请求无法跨站访问其他源的数据
  • 数据层面
    • 不同源层面,无法访问到对应的cookie、localStorage等敏感数据
    • csrf跨站请求伪造,当用户进入恶意网站时,恶意网站通过img标签或其他手段对用户已登录的网站发起请求,利用浏览器会自动携带cookie身份认证信息来实现盗用用户的身份

二、CORS跨站资源共享

CORS是http1.1的一种跨域解决方案,总体思路为:要请求数据资源,需要得到服务器的同意 CORS分为简单请求、预检请求、附带身份信息的请求

2.1 简单请求

简单请求需满足以下条件

  • 为以下请求方法
    • get
    • post
    • head
  • 请求头仅包含安全的字段,如
    • Accept
    • Width
    • Accept-Language
    • Content-Type
  • 其中若有Content-Type,则其值必须为
    • application/www-form-urlencoded
    • text/plain
    • multipart/form-data 当满足以上条件时,该请求为简单请求
// 简单请求
fetch('http://www.baidu.com',{
    method:'get'
})

fetch('http://www.baidu.com',{
    method:'post',
    headers:{
        Content-Type:'application/www-form-urlencoded'
    }
})

// 非简单请求
fetch('http://www.baidu.com',{
    method:'put'
})
fetch('http://www.baidu.com',{
    method:'post',
    headers:{
        Content-Type:'application/json'
    }
})

当判断某个请求为简单请求时,浏览器会自动在请求头中加入Origin字段来表示该请求来自哪里,且需要服务器在响应头中返回Access-Control-Allow-Origin字段来同意跨域请求 Access-Control-Allow-Origin 字段的值

    • :表示所有来源都可以访问
  • 具体的源:如http://www.baidu.com,表示仅百度来的源可以访问

image.png

2.2 预检请求

简单请求对服务器的危害不大,所以通过以上交互即可完成,当判断为不是简单请求后,浏览器会

  • 发起预检请求,询问服务器是否允许
  • 服务器同意后
  • 发起真实的请求
  • 服务器返回真实的数据响应
// 需要预检的请求
fetch('http://crossdomain.com/api/user', {
  method: 'POST', // post 请求
  headers: {
    // 设置请求头
    a: 1,
    b: 2,
    'content-type': 'application/json',
  },
  body: JSON.stringify({ name: '袁小进', age: 18 }), // 设置请求体
});
  1. 当浏览器判断为不是简单的请求后,会询问服务器,即发起预检请求options
OPTIONS /api/user HTTP/1.1
Host: crossdomain.com
...
Origin: http://my.com
Access-Control-Request-Method: POST // 请求的方法
Access-Control-Request-Headers: a, b, content-type // 请求中的特殊请求头

这个请求没有请求体,这是一个预检请求,包含我们真实想要请求的的事 预检请求有以下特征

  • 是一个options方法的请求
  • 没有请求体
  • 请求头中包含
    • Access-Control-Request-Method:后续要提交的请求方法
    • Access-Control-Request-Headers:后续要提交的请求中会改动的请求头
  1. 得到服务器允许后,即服务器会在响应头中返回
HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Origin:和简单请求一样,表示同意的请求源
  • Access-Control-Allow-Methods:表示同意的请求方法
  • Access-Control-Allow-Headers:表示同意改动的请求头
  • Access-Control-Max-Age:表示多少秒内,同样的请求源、请求头、请求方法无需在发送预检请求
  1. 当服务器同意跨域请求后,会继续发起真实的请求
POST /api/user HTTP/1.1
Host: crossdomain.com
Connection: keep-alive
...
Referer: http://my.com/index.html
Origin: http://my.com

{"name": "袁小进", "age": 18 }
  1. 服务器返回真实的响应数据
HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
...

添加用户成功

image.png

2.4 附带身份信息的请求

默认情况下,ajax请求不会携带cookie身份信息,但这样某些操作就无法完成,不过可以通过简单配置实现cookie的携带

// xhr
const xhr = new XMLHTTPRequest();
xhr.widthCredentials = true;

// fetch
fetch('https://www.baidu.com',{
  credentials: 'include'  
})

这样就变成了携带身份的跨域请求,当一个请求携带了cookie时,无论它是简单请求还是预检请求,都会在请求头中添加cookie字段,而服务器会在响应头中添加Access-Control-Allow-Credentials:true即可。
若服务器没有添加该字段,则跨域请求会失败。
另外,若使用了携带身份凭证的跨域请求,则Access-Control-Allow-Origin不能为 *,必须为具体的源

三、jsonp跨域请求

jsonp是指通过script标签跳过浏览器跨域获取数据,因为浏览器不会阻止script请求资源,但采用这种方式只能获取get请求的数据。
原理:是先在script脚本中定义一个callback方法,形参为服务器返回的数据,然后通过script标签访问服务器对应的端口,通过服务器配合返回一个script脚本,该脚本调用本地已经定义的callback方法,并将参数传递进入,这样就拿到了跨域资源。

    <button>点击获取用户</button>
    <script>
      function callback(resp) {
        console.log(resp);
      }

      function request(url) {
        const script = document.createElement('script');
        script.src = url;
        script.onload = function () {
          script.remove();
        };
        document.body.appendChild(script);
      }

      document.querySelector('button').onclick = function () {
        request('http://localhost:8000/api/user');
      };
    </script>
    
    // 服务器返回
    const express = require('express');

const app = express();
const path = '/api/user';
const users = [
  { name: 'monica', age: 17, sex: 'female' },
  { name: '姬成', age: 27, sex: 'male' },
  { name: '邓旭明', age: 37, sex: 'male' },
];
app.get(path, (req, res) => {
  res.setHeader('content-type', 'text/javascript');
  res.send(`callback(${JSON.stringify(users)})`);
});

const port = 8000;
app.listen(port, () => {
  console.log(`server listen on ${port}`);
  console.log(`request for users: http://localhost:${port}${path}`);
});

image.png

四、拓展

跨域请求时,js只能访问一些基本的响应头数据,如Content-Type、Expire、Cache-Control、Content-Language等字段,若想要访问其他字段,如自定义的a、b等其他字段,则需要服务器在响应头中配置Access-Control-Expose-Headers将对应的请求头字段加入白名单

Access-Control-Expose-Headers: authorization, a, b