跨域问题
出现跨域的原因是浏览器的同源策略
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html |
同源 | 只有路径不同 |
https://store.company.com/secure.html |
失败 | 协议不同 |
http://store.company.com:81/dir/etc.html |
失败 | 端口不同 ( http:// 默认端口是 80) |
http://news.company.com/dir/other.html |
失败 | 主机不同 |
解决方法一般使用跨域资源共享CORS
跨源资源共享
(CORS)(或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源
也就是说CORS主要通过HTTP头来判断接收还是拒接响应或者请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request),在非简单请求中,还好额外的发送一次请求
CORS需要浏览器和服务器同时支持,因为现在的浏览器基本都支持CORS,所以只要服务器实现了CORS接口,就可以跨源通信
模拟
接下来模拟一下跨域
开启两个端口不一样的服务,点击页面按钮的时候通过ajax调用另外一个服务的接口
$.ajax({
type: "get",
url: "http://localhost:8081/get",
success: function (data) {
alert(data)
}
})
当点击这个按钮是会跨域的
那么接口会不会被访问到呢
@GetMapping("get")
public String get(){
System.out.println("sccuess");
return "sccuess";
}
当点击按钮时,成功打印出了sccuess
,虽然在浏览器看状态码是200,但是在却看不到响应,这里使用的是谷歌浏览器
那么是浏览器拦截了响应,还是接口没有响应成功呢,抓个包看看
可以看到请求是由成功响应的
在火狐上是可以看到响应结果的,但是标明了
脚本无法获取响应主体(原因:CORS Missing Allow Origin)
分析
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
简单请求
上诉的请求属于简单请求,请求方法是GET,没有太多的请求头
处理简单请求,浏览器会带上Origin
头表明该请求来源
GET /get HTTP/1.1
Host: localhost:8081
//请求来源
Origin: http://localhost:8080
上面的头信息中,Origin
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin
指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误
这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200,也就是说是浏览器拦截了响应
抓包返回的响应头确实没有包含Access-Control-Allow-Origin
字段
解决方法通常是在应用上加允许跨域的配置,在SpringBoot上的解决方法有很多,这里直接在Controller上加一个注解
@CrossOrigin(origins = "*")
加上之后问题确实解决了,返回的响应头确实是多了几个字段
HTTP/1.1 200
Vary: Origin
//指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源
Vary: Access-Control-Request-Method
//用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头
Vary: Access-Control-Request-Headers
//这个字段是必须的,表明,该资源可以被任意外域访问
Access-Control-Allow-Origin: *
这里还出现了vary字段,Vary
是一个 HTTP 响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复 (response) 还是向源服务器请求一个新的回复
Vary
响应头就是让同一个 URL 根据某个请求头的不同而使用不同的缓存
但是我的设置的跨域策略是*
,不知道为什么会出现这个,猜测是可能是我在测试跨域的时候反复的开启关闭跨域配置
非简单请求
接下来看看非简单请求,看文档当请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
,或者加了一些自定义头等,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
下面把接口接收json参数,请求时发现
确实是发起了两次请求
第一次请求是OPTIONS
请求
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应
请求方法
Request URL: http://localhost:8081/get
Request Method: OPTIONS
请求头
//指定浏览器CORS请求会额外发送的头信息字段
Access-Control-Request-Headers: content-type
//该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法
Access-Control-Request-Method: POST
响应头
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
//该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法
Access-Control-Allow-Methods: POST
//表明服务器支持的所有头信息字段
Access-Control-Allow-Headers: content-type
//用来指定本次预检请求的有效期,单位为秒
Access-Control-Max-Age: 1800
如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段,就和上边的简单请求一样,检查到没有相关的头信息,就会报错
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3600
总结
CORS需要浏览器和服务器同时支持,现在的浏览器基本都支持,也就是说主要是服务端来支持
CORS简单点来理解的话可以认为是用头信息来验证是否可以实现跨域
1:用于阻止还是允许浏览器向其他域名发起请求;
2:用于接受还是拒绝其他域名返回的响应数据;
本文来自博客园,作者:阿弱,转载请注明原文链接:https://www.cnblogs.com/aruo/p/16602279.html