Spring Boot 使用 CORS 解决跨域请求问题
对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。那什么是跨域呢?跨域指的是从一个域名去请求另外一个域名的资源。即跨域名请求,跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的 “同源策略” 造成的,是浏览器施加的安全限制。跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。下面举个例子:判断下面 URL 是否和 htt
对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。
那什么是跨域呢?
跨域指的是从一个域名去请求另外一个域名的资源。即跨域名请求,跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的 “同源策略” 造成的,是浏览器施加的安全限制。跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。
下面举个例子:
判断下面 URL 是否和 http://www.baidu.com/a/a.html 同源
- http://www.baidu.com/b/b.html 同源
- http://www.baidu2.com/a/a.html 不同源,域名不相同
- https://www.baidu.com/b/b.html 不同源,协议不相同
- http://www.baidu.com:8080/b/b.html 不同源,端口号不相同
综上所述,在同源策略的限制下,非同源的网站之间不能发送 AJAX 请求。如有需要,可通过降域或其他技术实现。
下面实现一个案例来验证一下
案例
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("server")
public class TestController {
@RequestMapping("test")
public String test(HttpServletResponse httpResponse,HttpSession session){
return "阿三";
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<div id="msg">
</div>
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
url:'http://localhost:8090/server/test',
type:'get',
dataType:'json',
success:function(res){
console.log(res);
$("#msg").html(res.msg);
},
error:function(){
console.log("error");
}
})
});
</script>
</body>
</html>
由于两项目的端口不通,所以造成了跨域访问。
那么如何解决跨域问题?,跨域问题解决方案有很多,本文主要讲通过 CORS 协议解决跨域问题。
CORS简介
CORS 是一个 W3C 标准,全称是 "跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与。
对于开发者来说,CORS 通信与同源的 AJAX/Fetch 通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求
在 CORS 出现前,发送 HTTP 请求时在头信息中不能包含任何自定义字段,且 HTTP 头信息不超过以下几个字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(值限于 3 个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
这里给出一个例子:
GET /baidu HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.baidu.com
Host: www.baidu.com
对于简单请求,CORS 的策略是请求时在请求头中增加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求访问。
- 允许:则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
- 不允许:则不在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段
非简单请求
对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求 (preflight request) 。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。
下面的请求会触发预检请求:
- 使用了 PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH方法
- 人为设置了非规定内的其他首部字段,参考上面简单请求的安全字段集合,还要特别注意 Content-Type 的类型
- XMLHttpRequestUpload 对象注册了任何事件监听器
- 请求中使用了 ReadableStream 对象
以下是一个发起预检请求的例子:
发起请求的 origin 与请求的服务器的 host 不同,而且根据上面的条件判断,触发了预检
这里说一下这几个的含义:
- Access-Control-Allow-Origin 该字段必填。它的值要么是请求时Origin字段的具体值,要么是一个*,表示接受任意域名的请求
- Access-Control-Allow-Methods 该字段必填。它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
- Access-Control-Expose-Headers 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
- Access-Control-Allow-Credentials 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可
- Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求
-
当预检请求通过后,浏览器才会发送真实请求到服务器。这样就实现了跨域资源的请求访问。
Spring Boot CORS 实现
spring mvc 4.2 版本增加了对 CORS 的支持,通过 Spring Boot 可以非常简单的实现跨域访问。
解决办法:
CORS 全局配置-实现WebMvcConfigurer
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); }
这种方式是全局配置的,但是很多都是基于旧的 Spring 版本,比如 WebMvcConfigurerAdapter 在 Spring5.0 已经被标记为 Deprecated,我们通过看源码就可以知道:
/** * An implementation of {@link WebMvcConfigurer} with empty methods allowing * subclasses to override only the methods they're interested in. * * @author Rossen Stoyanchev * @since 3.1 * @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made * possible by a Java 8 baseline) and can be implemented directly without the * need for this adapter */ @Deprecated public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
拦截器实现
通过实现 Fiter 接口在请求中添加一些 Header 来解决跨域的问题
import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter(filterName = "CorsFilter ") @Configuration public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin","*"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); chain.doFilter(req, res); }
使用@CrossOrigin 注解
在 Controller 上使用 @CrossOrigin 注解,该类下的所有接口都可以通过跨域访问。
@RestController @RequestMapping("/test") //@CrossOrigin //所有域名均可访问该类下所有接口 @CrossOrigin("https://www.baidu.com") // 只有指定域名可以访问该类下所有接口 public class CorsTestController { @GetMapping("/test2") public String sayHello() { return "hello world"; } }
这里指定当前的 CorsTestController 中所有的方法可以处理 https://www.baidu.com 域上的请求。
我们点击 CrossOrigin 进去源码看一下:
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CrossOrigin { }
从注解 @Target 可以看出,注解可以放在 method、class 等上面,类似 RequestMapping,也就是说,整个Controller 下面的方法可以都受控制,也可以单个方法受控制。
原理解析
这里不管是通过哪种方式配置 CORS,都是在构造 CorsConfiguration,一个 CORS 配置用一个 CorsConfiguration类来表示,如下所示:
Spring 中对 CORS 规则的校验,通过委托给 DefaultCorsProcessor实现。处理流程如下:
- 判断依据是 Header 中是否包含 Origin。如果包含则说明为 CORS 请求,转到 2;否则,说明不是 CORS 请求,不作任何处理
- 判断 response 的 Header 是否已经包含 Access-Control-Allow-Origin,如果包含,证明已经被处理过了, 转到 3,否则不再处理
- 判断是否同源,如果是则转交给负责该请求的类处理
- 是否配置了 CORS 规则,如果没有配置,且是预检请求,则拒绝该请求,如果没有配置,且不是预检请求,则交给负责该请求的类处理。如果配置了,则对该请求进行校验
校验就是根据 CorsConfiguration 这个类的配置进行判断:
- 判断 origin 是否合法
- 判断 method 是否合法
- 判断 header是否合法
- 如果全部合法,则在 response header 中添加响应的字段,并交给负责该请求的类处理,如果不合法,则拒绝该请求
总结
本文主要介绍了 CORS 的知识,以及如何在 Spring Boot 中配置 CORS。希望对小伙伴的学习有所帮助。
更多推荐
所有评论(0)