跨域问题

出现跨域的原因是浏览器的同源策略

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)
            }
        })

当点击这个按钮是会跨域的

image-20220830210754218

那么接口会不会被访问到呢

    @GetMapping("get")
    public String get(){
        System.out.println("sccuess");
        return "sccuess";
    }

当点击按钮时,成功打印出了sccuess,虽然在浏览器看状态码是200,但是在却看不到响应,这里使用的是谷歌浏览器

image-20220830211132465

image-20220830211153876

那么是浏览器拦截了响应,还是接口没有响应成功呢,抓个包看看

image-20220830215625774

可以看到请求是由成功响应的

在火狐上是可以看到响应结果的,但是标明了

脚本无法获取响应主体(原因: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,也就是说是浏览器拦截了响应

image-20220830215804070

抓包返回的响应头确实没有包含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 根据某个请求头的不同而使用不同的缓存

但是我的设置的跨域策略是*,不知道为什么会出现这个,猜测是可能是我在测试跨域的时候反复的开启关闭跨域配置

非简单请求

接下来看看非简单请求,看文档当请求方法是PUTDELETE,或者Content-Type字段的类型是application/json,或者加了一些自定义头等,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)

下面把接口接收json参数,请求时发现

image-20220830224115365

确实是发起了两次请求

第一次请求是OPTIONS请求

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-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.ruanyifeng.com/blog/2016/04/cors.html

posted @ 2022-08-31 15:17  阿弱  阅读(1380)  评论(0编辑  收藏  举报