网络请求中的跨域资源共享

跨域是实际前后端分离开发当中不可避免的会出现的问题,在面试当中也经常问到相关内容,这部分知识还是挺重要的。

这里把跨域相关的知识点学习一下,记录一下。

同源策略

在研究跨域之前,首先得明白啥是“域”,以及我们为什么要把它跨过去。

先引入一下同源策略的定义吧。

MDN定义:

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

简单来说,这是浏览器的一种安全策略,对于非同源之间的信息交互,浏览器会拒绝,这样能够有效阻隔来自非安全的源的恶意攻击。同源策略是一种Web的规范,而浏览器是这些规范的实现者。假如Web当中没有同源策略,那我们在网络上最后一点点的隐私也就彻底没有了。

那么什么才能叫同源,什么叫不同源呢?同源是指同时满足三个相同的URL,三个相同指的是域名相同,端口相同,协议相同。只有当三者完全相同时才可以认为是同源,有任一条件不相同则判定为不同源。在同源下进行URL请求是一切正常的,但若是未经特殊处理的不同源请求,将会被阻止并抛出安全错误,这是浏览器对信息安全的一种自我保护机制。

个人认为,同源也可以理解为同域,即处于同一资源点或同一网络区域,对不同源之间进行的访问一般称之为跨域访问。

在前后端分离的开发当中,前端和后端部署的端口号不相同,甚至有时域名都不会相同,这很明显在请求时就会触发保护机制,导致前端无法发送HTTP请求。但可以通过一些设置来越过这个保护机制。

CORS

CORS的中文名字叫跨域资源共享,英文全称为Cross-Origin Resource Sharing,它定义了浏览器与服务器之间如何实现跨域通信。CORS背后的基本思路就是使用自定义的HTTP头部允许浏览器和服务器之间互相了解,以确实请求或相应应该成功还是失败。

CORS通信过程由浏览器自主完成,使用CORS进行的跨域通信与不使用CORS的同域通信代码是一样的,但浏览器会自动识别是否跨域,如果跨域,就会自动附加一些头信息,有时候还会多发一次请求。

浏览器将CORS请求分成两类:简单请求和非简单请求。满足简单请求有两个条件。

  1. 请求方法是下面三个之一:HEADGETPOST
  2. HTTP的头信息不超出以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

这两个条件有任一不满足,就属于非简单请求。

对于简单请求,会在头信息中额外添加一个Origin字段,它包含发送请求页面的域(协议,域名和端口),服务器接收到这个值后,根据这个值决定是否同意这个请求。如果服务器可以同意这个请求,则会在响应头部加入Access-Control-Allow-Origin头部,包含相同的源,或者*(表示此资源公开),如果服务器不同意这个请求,则返回的头部中没有这个东西,浏览器如果在响应中没有找到此头部,则认为跨域请求被拦截,会抛出一个错误,但这个错误不能通过HTTP状态码识别,因为正常返回的响应可能是200,但只是被拦截。

其实除了Access-Control-Allow-Origin,还有两个可选字段Access-Control-Allow-CredentialsAccess-Control-Expose-Headers,前者表示是否允许发送cookie,它的值只能设置为true,表示允许请求中携带cookie,默认CORS请求是不带cookie的,如果不想带cookie,这段删除即可。后者表示XMLHttpRequest对象的getResponseHeader()方法还能否拿到其他字段,

非简单请求是指对服务器有特殊要求的请求,比如请求方法为PUTDELETE,或者Content-Type字段类型为application/json

非简单CORS请求会在正式通信之前先增加一次预检请求,意思是首先请求服务器确认这样的请求是否可以被满足。这种预检请求机制允许使用自定义头部,允许使用除了GETPOST之外的方法,以及不同请求体内容类型。预检请求使用OPTIONS方法发送,其头部包含OriginAccess-Control-Request-MethodAccess-Control-Request-Headers,前两个必选,第三个可选。第一个头部与简单请求中的origin相同,第二个头部表示请求希望使用的方法,第三个头部是可选的,表示自定义头部列表,使用逗号分隔。

服务器在接收OPTIONS请求后,会在响应中发送这些头部信息与浏览器沟通:

  • Access-Control-Allow-Origin:与简单请求含义相同
  • Accecss-Control-Allow-Methods:本服务器允许的方法(一个用逗号分隔的列表)
  • Access-Control-Allow-Headers:本服务器允许的头部(一个用逗号分隔的列表)
  • Access-Control-Max-Age:缓存预检请求时间(单位为秒)

预检请求返回后,结果会按响应指定的时间缓存一定时间,在这段时间内,不会再重新发送预检请求。

也就是说,第一次发送非简单请求时会发送两个请求,第二次发送时,如果第一次的预检请求缓存没有过期,则不会再重新发送一次预检请求,但如果过期了,就会再发送一次预检请求。预检请求中也可以携带凭据请求设置。

实际解决跨域拦截的办法

我们在实际处理跨域问题当中是后端解决的,使用SpringBoot提供的@Configuration注解即可将其自动配置。

查阅资料可以知道axios也可以设定跨域,但我们在开发过程中尝试过一些方法均不成功,原因未知,最终是由后端添加配置解决了问题。

以下代码摘自我的Java Web课程设计,由驼君贡献的后端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.javaee.keshe.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @author:TuoJun
* @date:2021/01/04 17:33
* Description:
*/
@Configuration
public class CrosConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}

此外还有一个abo的版本,是我们之前项目里使用过的,也可以处理跨域问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.gk.gkserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
* @author abo
* @date 2020/9/8 20:36
* @remarks
**/
@Configuration
public class CorsConfig {

private CorsConfiguration corsConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig());
return new CorsFilter(source);
}
}

其他跨域解决方案

除了CORS之外,还有几种跨域解决方案,它们都有一定的局限性,但仍然有一些可圈可点之处。

  • 图片探测(image ping)

利用<img>标签实现跨域通信的技术,因为任何页面都可以跨域加载图片。可以动态创建一个图片,然后通过它们的onloadonerror事件处理程序得知何时收到响应。这种技术常用于图片探测,数据通过查询字符串发送,响应随意,不过一般是位图图片或者204状态码。

204 No Content 表示请求在服务端已经正常处理,但没有资源可以返回。一般来说不需要返回信息的请求可以返回204。

浏览器通过图片探测拿不到任何数据,但监听的onloadonerror事件可以让开发者知道什么时候可以收到响应(测试服务器连通性)

《JavaScript高级程序设计》中说,图片探测可以被用于跟踪用户在页面上的点击或动态显示广告,但我实在不理解这俩操作到底有什么用以及怎么实现。。并且在搜索引擎中也找不到相关结果,大概是一个被遗弃的办法吧。

  • JSONP

JSONP是“JSON with padding”的简写,是JSON的一种变形体,看起来和JSON一样,但会包含在一个函数调用里。

JSONP格式包含两个部分:回调和数据,回调是在页面接收到响应之后执行的函数,通常回调函数的名称是通过请求来动态指定的,而数据就是作为参数传回给回调函数后的JSON数据,

一个典型的JSONP请求例子:http://url.com/jsonp/?callback=handleResponse

这个例子里把回调函数的名字定为handleResopnse()

JSONP调用的原理和图片探测类似,是动态创建<script>元素实现的,因为<script>的src属性也不受到跨域的限制,因为JSONP是有效的JavaScript,所以JSONP响应在被加载完成后会立即执行。

JSONP的优点是简单直观好用,可以直接访问响应体,但他的缺点非常致命:不安全。因为JSONP是从任意不同的域拉取可执行代码,如果这个域返回了恶意内容,只能删除JSONP,没有其他解决办法,所以在使用JSONP时一定要确保目标域可信任。此外,JSONP无法很好的确定请求是否失败,尽管HTML5中<script>元素有onerror事件,但目前还没有被任何浏览器实现。所以使用当中有一种使用计时器的替代方法,这种方法并不准确,但至少管点用。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2018-2021 Shawn Zhou
  • Hexo 框架强力驱动 | 主题 - Ayer
  • 访问人数: | 浏览次数:

感谢打赏~

支付宝
微信