跨域在开发时经常遇到,因此总结一下
什么是跨域
CORS 是 w3c 标准,全称是“跨资源共享”(Cross-origin resource sharing)。它允许浏览器跨向跨源服务器发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制
简单请求和非简单请求
浏览器将 CORS 请求分为两类:
- 简单请求(simple request)
- 非简单请求(not-so-simple request)
只要同时满足以下两大条件,就属于简单请求
(1)请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP 的 header 不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
https://www.ruanyifeng.com/blog/2016/04/cors.html
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
搭建测试环境
先搭建一个环境再仔细讲解吧。总共有两个部分:
- 基于 SpringBoot 的后端
- 基于 jQuery 的前端
基于 SpringBoot 的后端
创建一个 controller
1 |
|
返回数据包装类 ApiResult
的定义如下
1 | public class ApiResult { |
这样访问 http://localhost:8080/test
时就能得到以下 json 数据
1 | { |
基于 jQuery 的前端
1 |
|
鼠标点击 <a>
标签,Chrome 浏览器的控制台就会报错
网页的域名是 http://127.0.0.1:8081/
,向另外一个域名 http://localhost:8080/test
发起请求,因为域名不同,因此被浏览器的 CORS policy 给 block 了
产生跨域的原因
有 3 个原因:
- 浏览器的限制
浏览器会自身不允许跨域
协议、域名、端口任何一个不一样都会产生跨域
发起的请求是 XHR(XMLHttpRequest)请求
只有 XHR(XMLHttpRequest)请求才会发生跨域,如果是普通的请求则不会发生。比如这样
1 | <img src="http://localhost:8080/test" /> |
如何解决
Filter
点击按钮,向 http://localhost:8080/test
发起请求。浏览器会校验请求返回时 HTTP 的 Response Header 里的跨域相关 header,比如 Access-Control-Allow-Origin
、Access-Control-Allow-Method
等。所以,可以利用 Filter 给 HttpServletResponse
添加上相应的 header
1 | public class CorsFilter implements Filter { |
然后配置一下
1 |
|
这时候再次发起请求,看一下 response header
这样浏览器就能允许 http://127.0.0.1:8081/
的网页向 http://127.0.0.1:8081
发起 HTTP GET 请求了
filter 还可以改一下允许向任何域发起任何 http 方法的请求
1 | httpServletResponse.addHeader("Access-Control-Allow-Origin", "*"); |
简单请求和复杂请求
只靠 Access-Control-Allow-Origin
和 Access-Control-Allow-Method
解决跨域是不够的,因为跨域请求分为简单请求和复杂请求
什么是简单请求?
同时满足以下两个条件
- HTTP 方法是下面的其中一种
- HEAD
- GET
- POST
- request header 中
- 没有自定义 header
- 并且
Content-Type
为以下几种中的一种:text/plain
application/x-www-form-urlencoded
multipart/form-data
什么是复杂请求?
除了简单请求以外的就是复杂请求。比如常见的几种
- PUT、DELETE 的 ajax 请求
- 发送 json 格式数据的 ajax 请求
- 带自定义 header 的 ajax 请求
预检请求 HTTP OPTIONS
如果是复杂请求,浏览器会发出一个 HTTP OPTIONS 请求,也叫预检请求,用来判断这个复杂请求是否会发生跨域
现在来测试一下。新增一个接收 json 格式数据的 controller 方法
1 | "postJson") ( |