同源策略
浏览器为了安全考虑,如果一个网页访问一个协议,域名或者端口和自己不一样的资源,浏览器就会认证出现了跨域,包括img访问图片,script标签加载脚步等,但是对于这些跨域浏览器没有什么限制,其对ajax的跨域限制最为明显,要么浏览器不给对应的请求返回服务器的响应,要么就不发送对应的请求。常用的解决方案有三种:
- 代理
- CORS
- JSONP
代理
代理适用于跨域发生在开发环境中,在生产环境中没有跨域的情景。我知道的代理处理场景都是需要构建工具的参与的,以webpack为例:在构建webpack的时候,一般都是安装webpack-dev-server开发服务器,用于本地启动一个服务器运行我们编写的项目,在webpack.config.js中可以通过devServer配置需要代理的接口
CORS
如果通过CORS解决跨域问题,那么一般不需要前端的参与,因为这是浏览器和服务器的交互。
当一次请求是跨域请求的时候,浏览器会在请求头中自动添加origin请求头,表示这一次的请求源是谁。
cors处理跨域分为三类场景:
普通请求
只要满足一下条件就可以认为这次的请求是普通请求
- 请求方法为:get, post, head其中一种
- 使用的请求头都是安全的,没有自定义的请求头
- content-type的取值为:text/plain, application/x-www-form-urlencode, multipart/form-data中的一种
如果本次请求是普通请求,那么服务器返回的报文中只需要携带Access-Control-All-Origin: 当前网页的源。那么浏览器就会认为服务器运行跨域访问就会将响应的报文返回给对应的请求。
普通请求造成的跨域:
服务器设置Access-Control-Allow-Origin: http://127.0.0.1:8080:
成功获取到服务器的返回数据
检查响应报文:
服务器明确表明了运行http://127.0.01:5173访问自己的资源,所以浏览器就没有拦截响应的报文了
服务器也可以通过设置Access-Control-Allow-Origin: * 表示任何网站都可以访问我的资源,但是当请求携带cookie的时候,*的写法不会通过浏览器的校验,浏览器就不会发送对应的请求。
预检请求
不是普通请求就是预检请求,当一个请求是预检请求的时候,浏览器不会立即发送这次的请求,而是会先构建一个报文,询问服务器是否允许这样的请求发送给它。浏览器的控制台不会显示预检请求的记录,只能通过服务器查看:
预检请求的请求方式Options,通过会在请求头中携带跨域请求的请求方法,由于造成本次请求不是普通请求的原因是,请求的方法为put,所以请求头通过Access-Control-Request-Method: PUT, 询问服务器是否允许put请求进行的跨域访问,如果不允许,那么浏览器就不会发送这一次的请求,同时抛出错误。
如果服务器运行对应的请求方式进行跨域访问,那么可以在响应报文中设置Access-Control-Allow-Methods: 请求方法,注意这里是Methods。浏览器收到报文以后,就会发送对应的报文,服务端代码:
如果是由于携带了特殊的请求头造成的预检请求,那么在预检请求的请求头中还会携带,Access-Control-Request-Headers: 对应的请求头,这里由于是添加了自定义的请求头auth,所以值为auth。
如果有多个不安全的请求头,Access-Control-Request-Headers为这些请求头的集合:
同理如果服务器运行携带这些请求头的请求访问自己,那么只需要在返回的报文中携带:Access-Control-Allow-Headers: 对应的请求头即可。
如果不想没有发起跨域请求是都先发一次预检请求,那么服务器可以设置响应头Access-Control-Max-Age,表示在多少秒以内,同样的源发起的请求可以不发生预检请求了。
未设置前发起请求:
每一次都会触发预检请求,设置Access-Control-Max-Age以后:
注意不是所有的浏览器都支持Access-Control-Max-Age, 有些浏览器即使设置了Access-Control-Max-Age,该触发预检请求还是会触发预检请求。上面的成功实例是在edge浏览器中实现的。
携带cookie的请求
当携带cookie以后,这个请求就一定会触发预检请求,服务器必须明确指定Access-Control-Allow-Origin 为跨域站点的源,不能是*号。
在fetch请求中设置请求的时候携带cookie:
虽然我在响应报文中指定了允许http://127.0.0.1:5173源,通过put请求携带a,auth,和b请求头,跨域访问服务器,但是浏览器依然没有运行本次请求发送处理,并触发跨域异常。
警告已经明确的告诉我们,响应还需要携带Access-Control-Allow-Credentials为true的请求头。
通过设置Access-Control-Allow-Credentials 为true,请求就能正常获取数据了。
补充
虽然通过设置
- Access-Control-Allow-Origin:指定运行跨域访问的源
- Access-Control-Allow-Methods: 指定运行跨域访问的请求方法
- Access-Control-Allow-Headers: 指定跨域访问可以携带的请求头
- Access-Control-Allow-Credentials: 指定是否允许携带cookie进行跨域
通过上述字段,可以得到其他源的数据,但是浏览器仍然不放心,所以浏览器并不会将所有的响应头都暴露个js,比如服务器自定义的响应头:
在浏览器中通过js获取响应头,并打印出来:
没有得到服务器自定义的响应头。
此时如果服务器运行浏览器获取相应的响应头,那么需要返回另一个响应头:Access-Control-Expose-Headers,指定需要暴露给浏览器的响应头
此时对应的js请求就能得到相关的响应头了。