可能是2022最新最全的cors跨域讲解,万字血书

313 阅读7分钟

前言

网上有很多跨域的文章,但是要么信息过时,要么不全,所以记录一下目前最新的情况,2022.07

跨域有哪些

跨域是什么不用多说了吧,我们看看各种跨域请求有啥问题

  • ajax跨域,包括get、post、自定义header等情况
  • ajax跨域,携带凭证,一般是cookie
  • iframe跨域
  • 跨域,安全问题
  • 其他跨域方法
  • iframe跨域通信

说明

配置了3个域名,并且配置了3个域名的https

cors-test1和cors-test2为同级

cors-test3.yyyy.com.cn和 cors-test1、cors-test2 分别的顶级域名

http://cors-test1.xxxx.com.cn
http://cors-test2.xxxx.com.cn
http://cors-test3.yyyy.com.cn

服务器文件:

/test-cors.html 测试前端页面
/test.php  测试后端页面

浏览器版本:

别的浏览器没测试,目前看chrome应该是最严格的,所以可以按照chrome来

chrome 102.0.5005.115

ajax跨域,包括get、post、自定义header等情况

这里发送请求时,不会携带cookie,但是可以有请求参数、自定义头等

发送简单请求

简单来说就是get,post请求,并且只能包含下面的header:

Accept
Accept-Language
Content-Language
Content-Type(需要注意额外的限制)
Content-Type 的值仅限于下列三者之一:
	text/plain
	multipart/form-data
	application/x-www-form-urlencoded

发送个简单的请求,错误示例:

由cors-test1向cors-test2发送请求

    fetch('http://cors-test2.xxxx.com.cn/test.php',{
    }).then(response => response.text()).then(res =>{
      console.log(res)
    })

image.png 可以看到会得到一个cors错误,鼠标移上去会有详细的错误提示,没法截图,这里会提示:MissingAllowOriginHeader,就是响应头部缺少Access-Control-Allow-Origin

发送个简单的请求,正确示例:

所以我们修改一下test.php,增加header

header("Access-Control-Allow-Origin: *");

echo 'ok';

image.png

image.png

image.png

可以看到我们在服务端增加了一个headerAccess-Control-Allow-Origin就可以实现跨域请求,是不是很简单?但是实际我们还有很多别的情况

发送非简单请:

非简单请求,就是除了简单请求之外的,一般是有自定义header情况

    fetch('http://cors-test2.xxxx.com.cn/test.php',{
      headers:{ 
        token:'bbkjsi232',//自定义header
      }
    }).then(response => response.text()).then(res =>{
      console.log(res)
    })

image.png

image.png

可以看到浏览器会发送2个请求,第一个请求叫预检查请求,这个请求是浏览器自动发送的,请求类型为OPTIONS,预检查就是我先发送一个检查请求,服务端判断一下要不要我发送真正的请求

这时我们需要别的几个头了

Access-Control-Allow-Origin //允许的源,不能为通配符*了
Access-Control-Allow-Headers  //允许的头
Access-Control-Allow-Methods //允许的请求方法,post,get等
Access-Control-Max-Age //缓存时间,这个时间内,浏览器不会再发送预检查,chrome最大时间为600秒

发送非简单请求,正确示例:

这时服务端的代码像这样的

//如果是options请求,只返回header
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS'){
    header("Access-Control-Allow-Origin: http://cors-test1.xxxx.com.cn");
    header('Access-Control-Allow-Headers: token');
    header('Access-Control-Allow-Methods: POST,GET');
    header('Access-Control-Max-Age: 10');
    exit;
}

header("Access-Control-Allow-Origin: *");

echo 'ok';

预检查请求

image.png

image.png

响应ok

image.png

ajax跨域,携带凭证,一般是cookie

凭证包括 HTTP cookies 和 HTTP 认证信息,但一般我们用cookie

发送请求,携带cookie:

fetch:credentials: "include"

xhr:withCredentials :true


    fetch('http://cors-test2.xxxx.com.cn/test.php',{
      mode:'cors',
      credentials: "include",
    }).then(response => response.text()).then(res =>{
      console.log(res)
    })
    
    
   axios.get('http://cors-test2.xxxx.com.cn/test.php',{
      withCredentials :true
   })

image.png

image.png

目前header只返了Access-Control-Allow-Origin 并且为*,是不支持的

image.png

发送请求,携带cookie,正确示例:

这是需要2个响应头部

Access-Control-Allow-Credentials:true   //允许凭证
Access-Control-Allow-Origin:http://cors-test1.xxxx.com.cn  //origin必须指定名字,不能是通配符

image.png

image.png

可以看到没报错了,也返回数据

看看我们的请求

image.png

可以看到已经携带了cookie了,那么是谁的cookie呢?

image.png

可以看到实际发送的是目标网址的cookie,而不是自己的cookie

发送请求,携带cookie,跨顶级域名:

我们再来看看跨顶级域名,给cors-test3.yyyy.com.cn域名发送看看

    fetch('http://cors-test3.yyyy.com.cn/test.php',{
      mode:'cors',
     credentials: "include",
    }).then(response => response.text()).then(res =>{
      console.log(res)
    })

cookie情况

image.png

我们的cookie,cors-test3是有值的username=test3,按照刚才的情况,应该会发送username=test3

看看我们的请求

image.png

cookie并没有发送,并且服务端设置cookie也报错

这是就需要cookie的另一个属性,SameSite

SameSite 接受下面三个值:

Lax

Cookies 允许与顶级导航一起发送,并将与第三方网站发起的 GET 请求一起发送。这是浏览器中的默认值。

Strict

Cookies 只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送。

None

Cookie 将在所有上下文中发送,即允许跨站发送。

以前 None 是默认值,但最近的浏览器版本将 Lax 作为默认值,以便对某些类型的跨站请求伪造(CSRF)攻击具有相当强的防御能力。

SameSite=None 需要 Secure ,如果设置了Secure,则需要站点为https

接下来我们设置一下cookie,访问地址 https://cors-test3.yyyy.com.cn/test-cors.html 注意https

//设置一个cookie,username-none
document.cookie = 'username-none=test3-none;SameSite=None;Secure'

image.png

再发起请求,访问地址:http://cors-test1.xxxx.com.cn/test-cors.html,站点为http

注意目标地址为https

    fetch('https://cors-test3.yyyy.com.cn/test.php',{
      mode:'cors',
     credentials: "include",
    }).then(response => response.text()).then(res =>{
      console.log(res)
    })

image.png

可以看到发送请求的时候携带了username-none ,并没有携带 username

跨顶级域名,只能发送SameSite=None的cookie,这需要目标站点为https

iframe跨域

iframe跨域一般就是嵌套别的页面,这里也是跨域cookie问题

先看看同顶级域名

<iframe src="http://cors-test2.xxxx.com.cn/test.php"></iframe>

image.png

可以看到请求发送了cookie,响应设置cookie也成功了

跨顶级域名,http嵌套

<iframe src="http://cors-test3.yyyy.com.cn/test.php"></iframe>

image.png

发送并没有cookie,并且设置cookie也有问题

跨顶级域名https嵌套

image.png

可以看到,只能发送none的cookie,服务端设置cookie也必须要是none和Secure

跨域,安全问题

跨域安全问题实际上就是csrf攻击,csrf就是如果你现在登录了淘宝,同时新标签页打开了另外一个网站,这时另外一个网站通过ajax发起请求给淘宝, 不就是我们现在所讲的跨域情况吗?如果这时候浏览器没有限制自动发送了cookie给淘宝,而淘宝也是用cookie作为认证的话,那么不是会遭到破坏请求,例如拉取购物车等

iframe框问题也是一样的,如果另外一个网站用iframe框嵌套淘宝的页面,如果携带了cookie,那么你可能认为另外一个站点就是淘宝了,有可能让你乱操作

所以现在浏览器默认cookie都SameSite=Lax, None并不安全

另外现在都应该使用token了吧,也就是你自己负责把token记录下来,可以存到内存变量或者localStorage,存cookie并不是那么安全,因为浏览器会在一些情况自动发送

其他跨域方法

1.JSONP

这个原理自己网上查找吧

2.代理

也就是让自己的服务端去代理请求

iframe跨域通信

刚才讲的都是前端和服务端的通信,现在前端嵌套的iframe需要互相通信

当然也需要对iframe的内容能够修改才行

1.postMessage

网上查

2.websocket

2个站点都链接同一个服务器,相当于服务端做了一个实时中转

3.location.hash

就是设置iframe的链接的hash值,通过监听hash变化

总结

服务端响应头:


Access-Control-Allow-Origin //允许的源,不能为通配符*了
Access-Control-Allow-Headers  //允许的头
Access-Control-Allow-Methods //允许的请求方法,post,get等
Access-Control-Max-Age //缓存时间,这个时间内,浏览器不会再发送预检查,chrome最大时间为600秒

预检查请求

对于非简单请求,浏览器会自动发送一个OPTIONS请求,验证是否运行跨域

发送请求是携带凭证

fetch:credentials: "include"

xhr:withCredentials :true

cookie SameSite属性

SameSite 接受下面三个值:

Lax

Cookies 允许与顶级导航一起发送,并将与第三方网站发起的 GET 请求一起发送。这是浏览器中的默认值。

Strict

Cookies 只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送。

None

Cookie 将在所有上下文中发送,即允许跨站发送。  //需要https

参考

cors说明 developer.mozilla.org/zh-CN/docs/…

http header developer.mozilla.org/zh-CN/docs/…

fetch标准 fetch.spec.whatwg.org/

cookie各字段说明 developer.mozilla.org/zh-CN/docs/…

另外如果觉得不错,可以点个赞再走吧