摘要:前后端独立开发期间,交互主要通过接口文档,前端Mock数据,后端使用Postman都不会发现跨域问题。当联调时前端尝试调用后端接口,这往往就需要需要处理的跨域问题……
下面总结下跨域问题产生的前因后果以及如何通过CORS解决:
1. 什么是跨域?
定义跨域之前,先介绍另外一个概念:同源策略。MDN给出的定义如下:
同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。 如果两个 URL 的协议、端口(如果有指定的话)和主机都相同的话,则这两个 URL 是同源的。
浏览器的同源策略 - Web 安全 | MDN同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
浏览器的同源策略能阻隔恶意文档,减少可能被攻击的媒介。判断是否同源,是同源策略是否生效的关键。
综上可知,跨域的是由于浏览器同源策略的安全限制,当一个域下的文档或脚本试图去请求另一个域下的资源时,如果域名、端口、协议任一不同,都会被浏览器视为跨域。
需要注意:
满足某些限制条件的情况下,页面是通过document.domain修改它的源,以通过同源检测。
需要注意的是跨域通常是浏览器的限制,实际请求已经正常发出且服务端已响应。
2. 跨域的解决方案有哪些?
CORS (Cross-Origin Resource Sharing)
CORS 是 HTTP 的一部分,它允许服务端来指定哪些源可以从这个服务端加载资源。CORS方案的关键在于后端会在响应头中添加Access-Control-Allow-*头,浏览器将据此通过请求。
CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
同时满足以下条件则属于简单请求:
请求方法属于:HEAD、GET、POST
HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、 Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/pla
跨域资源共享 CORS 详解 - 阮一峰的网络日志https://www.ruanyifeng.com/blog/2016/04/cors.html
对于简单请求,浏览器直接发出CORS请求,并在头信息之中增加Origin字段
GET /cors HTTP/1.1
Origin: http://imooc.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Origin非指定源,服务器返回http正常响应,没有包含Access-Control-Allow-Origin字段;则浏览器会报错并被XMLHttpRequest的onerror捕获(这种错误无法通过状态码识别,可能状态码为200)。如果Origin在指定域名中响应中会多出如下字段
Access-Control-Allow-Origin: http://imooc.com // 请求的Origin或者*
Access-Control-Allow-Credentials: true // 是否允许发送Cookie
Access-Control-Expose-Headers: FooBar //
Content-Type: text/html; charset=utf-8
非简单请求的CORS请求,会在正式通信之前,增加一次OPTIONS查询请求,称为"预检"请求(preflight)。预检"请求"通过服务端返回的Access-Control-Allow-判断请求是否被允许。如果否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段,浏览器处理过程同简单请求非指定源。一旦预检"请求"包含Access-Control-Allow-,则通过预检,会跟简单请求一样再次发送请求。
综上,CORS的本质是通过后端设置的响应头信息,告诉前端该请求是否支持跨域。
CORS通常不需要前端改动,CORS请求默认不发送Cookie和HTTP认证信息;如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials: true;前端请求也需要配置xhr.withCredentials = true时,此外后端必须增加 response 头信息Access-Control-Allow-Origin(CORS),且必须指定具体域名,而不能指定为*。
axios.defaults.withCredentials = true;
下文中的后端配置CORS会由于预检请求将会导致部分接口跨域不生效的问题。
反向代理
反向代理是在页面同域下配置一套反向代理服务,页面请求同域的服务端,服务端请求上游的实际的服务端,之后将结果返回给前端,完成将跨域请求转换为同源请求的操作。
反向代理只需要后端处理即可,对前端同学来说太nice了!
JSONP
JSONP是利用<script>标签不存在跨域限制,历史悠久且只支持GET请求,且需要前后端配合支持。
3. Spring Boot中如何支持CORS
Spring Boot中通过CORS解决跨域问题可以有多种实现方式:
参考官网给出的示例:
CORS support in Spring FrameworkLevel up your Java code and explore what Spring can do for you.https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
3.1 使用注解@CrossOrigin,配置方法或者类的跨域
// **@CrossOrigin应用于单个方法**
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin //
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
// **对Controller中所有方法使用@CrossOrigin
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)**
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
注:当同时使用控制器级别和方法级别的CORS配置,Spring将合并两者的注解属性以创建一个合并后的CORS配置。
3.2 添加全局配置解决跨域问题,则需要一个配置类:
此处,通过全局配置CORS解决关于问题,增加配置类如下:
@Configuration
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
@Override
// 公共方法用于配置CORS跨域访问规则,接受一个CorsRegistry对象作为参数,用于配置CORS规则
public void addCorsMappings(CorsRegistry registry) {
// registry对象调用addMapping方法,指定允许跨域请求的路径为所有路径("/**")
registry.addMapping("/**")
.allowedOrigins("*") // 设置允许跨域请求的来源,此处为允许所有来源
.allowedMethods("GET", "POST", "PUT","OPTIONS", "DELETE") // 设置允许的HTTP请求方法
.maxAge(3600) // 设置预检请求的有效期,单位为秒
.allowCredentials(true); // 设置是否允许请求携带认证信息(如cookies、HTTP认证及客户端SSL证明),此处允许请求携带认证信息
}
}
常见的 CORS 配置项:
- allowedOrigins(String... origins):允许访问的来源,可以是一个字符串数组。
- allowedMethods(String... methods):允许的 HTTP 方法,如 GET、POST 等。
- allowedHeaders(String... headers):允许的请求头。
- exposedHeaders(String... headers):允许暴露给客户端的响应头名称。
- allowCredentials(boolean allowCredentials):是否允许发送身份验证信息(如 cookies)
- maxAge(long maxAge):预检请求的缓存时间,以秒为单位。
- allowedPublicApis(String... publicApis):允许的公共 API。
启用上述全局配置后出现另外一问题:部分接口有效(可发送跨域请求),部分接口依然提示跨域问题。尝试通过对单个接口或者类中配置@CrossOrigin依然无效。发现此时单独访问接口(双击网络面板的接口)会提示"NEED_JWT_TOKEN",应该是拦截位置导致的,部分接口拦截了部分过滤器未拦截,最终定位产生这个问题的原因是由于:
"预检"请求(preflight)
请求方法:OPTIONS
根据Header信息判断是否为预检请求,这个请求可以避免直接请求操作了服务端数据,却没被前端获取响应,是对后端数据一种保护。此处,为避免预检请求被拦截,对OPTIONS请求直接放行。
HttpServletRequest request = (HttpServletRequest) servletRequest;
if ("OPTIONS".equals(request.getMethod())) {
// 放行预检请求,按照原有逻辑执行
filterChain.doFilter(servletRequest, servletResponse);
}
3.3 使用mvc XML命名空间来配置CORS
官网给出的示例,可按照如下格式声明多个CORS映射
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="false"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />
</mvc:cors>
3.4 基于过滤器的CORS支持
除了上述方式Spring Framework还提供了CorsFilter,在过滤器中声明格式如下:
@Configuration
public class MyConfiguration {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
**config.setAllowCredentials(true);
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");**
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}