同源和跨域

比较杂的知识点,同源跨域、服务器代理、简单请求非简单请求

为什么要有同源策略

  1. 本质上是浏览器有cookie这么个玩意,比如移动端app就没有同源限制
  2. 防止CSRF攻击

如何解决跨域问题

服务器代理

  • 开发环境webpack-dev-server
  • 生产环境nginx代理服务器部署在同源域名下,转发请求到对应的服务器上(印象中公司项目的配置:某个域名下的某个路径,关联到后端appkey上)

CORS

| 本质上是通过服务端来允许
配置受信任的域名,允许受信任域名的跨域请求

简单请求和非简单请求

满足以下条件就是一个简单请求:

  1. Method: 请求的方法是 GETPOSTHEAD
  2. Header: 请求头是 Content-TypeAccept-LanguageContent-Language
  3. Content-Type: 请求类型是 application/x-www-form-urlencodedmultipart/form-datatext/plain

当一个请求跨域且不是简单请求时就会发送 OPTIONS 预检请求

而在项目中常见的 Content-Type: application/jsonAuthorization: <token> 为典型的非简单请求,在发送请求时往往会带上 Options

简单请求和非简单请求的CORS校验过程
  • 简单请求
    请求头部带上 Origin 字段,标识发出请求的源地址;
    服务端响应头 Access-Control-Allow-Origin 返回允许请求的源;
    浏览器收到服务器的响应后,会检查 Access-Control-Allow-Origin 允许的源和当前请求源是否一致,若一致,返回对应请求。若不一致,浏览器拦截请求,报跨域错误。

  • 非简单请求
    比如发送了一个 PUT 请求到 https://api.example.com,并且会带上 Authorization 头
    会先发送预检请求:

    1
    2
    3
    4
    5
    OPTIONS /users/1 HTTP/1.1
    Host: api.example.com
    Origin: https://www.myapp.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: Authorization

    服务器收到 OPTIONS 请求后,会根据自己的配置来判断是否允许该请求。如果允许,它会返回一个带有 Access-Control-Allow-* 系列头的响应。

    1
    2
    3
    4
    5
    6
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://www.myapp.com
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    Access-Control-Allow-Headers: Authorization, Content-Type
    Access-Control-Allow-Credentials: true
    Access-Control-Max-Age: 86400 // 预检结果的有效期,单位秒

    Access-Control-Allow-Origin: 明确允许的来源域。
    Access-Control-Allow-Methods: 明确允许的请求方法。
    Access-Control-Allow-Headers: 明确允许的请求头。
    Access-Control-Max-Age: 告诉浏览器预检结果可以缓存多久,在缓存有效期内,再次发送同样的请求就不需要再预检了。

    浏览器收到服务器的响应后,会检查 Access-Control-Allow-* 头信息是否与自己想要发送的请求匹配
    如果匹配:浏览器认为服务器允许该请求,于是发送真正的 PUT 请求。
    如果不匹配或服务器返回错误:浏览器会拦截请求,不会发送真正的请求,并在控制台报一个跨域错误。

JSONP

jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
function createScriptTag() {
var script = document.createElement('script');
script.type = 'text/javascript';
// 设置请求的URL,假设服务端接口为http://example.com/api/data,同时指定回调函数名为handleData
script.src = 'http://example.com/api/data?callback=handleData';
document.body.appendChild(script);
}

// 定义回调函数
function handleData(data) {
console.log(data);
// 在这里可以对返回的JSON数据进行处理,如更新页面内容等
}

服务器会返回一个函数调用,类似于 handleData({"data": "value"}) 的内容