目录
[三、Spring Security中CORS的开启](#三、Spring Security中CORS的开启)
一、CORS是什么
CORS(Cross-Origin Resource Sharing,跨源/域资源共享 )是一个W3C标准,一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本(比如AJAX)请求访问的机制,通常由于同源安全策略,浏览器会禁止这种跨域请求。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附件的头信息,有时还会多处一次附件的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS功能(响应报文包括了正确的CORS响应头),就可以被跨源访问(可以指定个别域或全部域)。
二、同源安全策略
说到跨域请求,就不得不说一下"同源安全策略"了,如果没有这个策略的存在,也就没有这么多事了,但是,这样可能会造成你在网站进行一些操作时存在风险。
同源策略是一个重要的安全策略,它用于限制一个源/域的文档或它加载的脚本是否能与另一个源/域的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
出于安全性,浏览器会限制脚本内发起的跨域HTTP请求,例如常见的 XHR、Fetch API 都遵循同源策略。
如果两个 URL 的协议(Protocol)、主机(Host)、端口(Port,如果有指定的话)都相同的话,那么这两个 URL 是同源的,否则是不同源的。
当浏览器中打开的某个网页,有脚本执行了跨域请求,那么,浏览器的"同源安全策略"就会介入,大致流程如下:
浏览器直接发出CORS请求,也就是在头信息之中,增加一个Origin
字段。这个字段代表本次请求来自哪个源(协议 + 主机 + 端口),服务器会根据这个值,决定是否同意这次请求。前面的流程是对于HTTP简单请求,如果是HTTP非简单请求,则会在正式请求前,增加一次预检请求。
如果Origin
指定的源,不在许可范围内(服务器端CORS功能指定),服务器会返回一个正确的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态识别,因为HTTP回应的状态码有可能是 200
。
抛出的错误为"has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.",如下:
三、Spring Security中CORS的开启
在Spring框架中,我们可以在引入Spring Security依赖后,对Security的HttpSecurity进行设置,来开启CORS(跨域/源资源共享),同时能指定只被部分域/源、部分方法、部分头部信息访问资源。
Spring框架提供了CorsFilter,是为了在基于filter的安全框架(如Spring Security)上面支持CORS,或者在使用其他不支持CORS的库上支持CORS。
java
//(Security6.2.4写法)先创建一个普通JAVA类,如SecurityConfig.java,实现如下3个Bean。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
(authz) -> authz
.anyRequest().authenticated())
.httpBasic(withDefaults())
.formLogin(withDefaults())
.csrf((csrf)->csrf.disable())
.cors(withDefaults()); //开启CORS(跨域/源资源共享)
return http.build();
}
@Autowired //自动装配参数configurationSource(下方的Bean)
@Bean
public CorsFilter corsFilter(UrlBasedCorsConfigurationSource configurationSource){
return new CorsFilter(configurationSource);
}
// 也可以将方法内的实现整合到上面的corsFilter方法体内
@Bean
public UrlBasedCorsConfigurationSource configurationSource(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOriginPattern("http://localhost*"); //新版本写法
//corsConfiguration.addAllowedOrigin("*"); //老版本写法
source.registerCorsConfiguration("/**",corsConfiguration);
return source;
}
四、其它处理方法
1、Spring注解
原理:自Spring Framework 4.2开始,CORS请求(包括OPTIONS method)会被自动分发到各种注册过的HandlerMappings。它们会处理CORS的preflight请求,会拦截CORS简单和实际请求,以便基于你指定的CORS配置,添加相关的CORS响应头(如 Access-Control-Allow-Origin
)。
实现:在@RequestMapping注解过的controller method上面添加@CrossOrigin注解,表示已开启CORS。
java
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600) //括号内为指定的CORS配置
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://domain2.com") //同时使用controller级别和method级别的CORS配置,Spring会将二者的attributes结合起来,创建出融合的CORS配置。
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
除了基于注解@CrossOrigin的配置(细粒度),还可以定义全局的CORS配置。类似于使用filters,但可以定义在Spring MVC中,并与细粒度的@CrossOrigin配置相配合。
2、JSONP(JSON with Padding)
原理:利用<script>标签不受浏览器同源策略限制的特性,通过动态插入<script>标签的方式实现跨域数据访问。
实现:客户端创建一个<script>标签,将请求后端的接口URL拼接一个回调函数名称作为参数传给后端,并设置给<script>标签的src属性。后端接收到请求后,将数据和回调函数名称拼接成函数调用的形式返回。客户端在接收到响应后,会执行这个回调函数,从而获取到后端返回的数据。
3、使用代理服务器
原理:通过搭建一个代理服务器来转发客户端的请求,代理服务器与目标服务器进行通信,然后将返回的数据再转发给客户端。由于客户端和代理服务器、代理服务器和目标服务器之间的通信都在服务器端进行,因此不受浏览器同源策略的限制。
实现:可以使用Nginx、Node.js等搭建代理服务器。例如,Nginx可以通过配置反向代理来解决跨域问题;Node.js可以搭建一个中间层服务器,接收客户端的请求,然后将请求转发给目标服务器,并将返回的数据再转发给客户端。
4、前端正向代理
原理:在客户端设置一个代理服务器,并指定目标服务器。代理服务器向目标服务器转交请求,并将获得的内容发送给客户端。这种方式在客户端进行了请求转发,但同样实现了跨域请求。
实现:可以通过在客户端代码中配置代理服务器地址和端口,以及目标服务器的地址和端口来实现。在发送请求时,将请求地址替换为代理服务器的地址,并在代理服务器中进行请求转发。
5、配置浏览器插件或工具
有些浏览器插件或工具(如Postman、curl等)可以绕过浏览器的同源策略限制,直接发送跨域请求。但这种方法主要用于开发和测试阶段,不建议在生产环境中使用。