CORS 简介
在解决问题之前先了解一下什么是CORS, CORS即为了解决浏览器同源问题,W3C 提出的跨源资源共享,即 CORS(Cross-Origin Resource Sharing)。也就是在前后端分离的情况下,后端服务允许前端服务访问自己的资源。
CORS 做到了如下两点:
不破坏即有规则
服务器实现了 CORS 接口,就可以跨源通信
Access-Control-Allow-Origin: http://www.examples.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
所以解决跨域问题的基本思路就是在请求头中加入以上内容:
每个header 的值可以根据自己的需求指定,也可以使用 “*” 代替,表示全部通过
他们的含义分别是:
- Access-Control-Allow-Methods: 真实请求允许的方法
- Access-Control-Allow-Headers: 服务器允许使用的字段
- Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
- Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求
当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求。
Spring boot + Spring security 跨域问题
有些时候我们可能遇到本来在spring boot 应用中通过CorsFilter 或者拦截器 配置了跨域,用的好好地,但是加入spring security 后跨域失效了的情况。
对于简单请求:
浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段
GET /cors HTTP/1.1
Origin: http://api.bob.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回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
对于非简单请求:
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
在实际应用中,由于前后端分离并且使用了security ,前端发送的请求头中需要添加 authorization 或者 token 的字段,因此会触发浏览器在发送请求之前会发送一个 OPTION 方法的预检请求。
则原先spring boot中的跨域配置就可能失效了,无法给预检请求返回 http OK。所以汇报跨域错误。
正确配置方式
方式1:
在SecurityConfig中配置开启CORS, 亲测有效
@Override
protected void configure(HttpSecurity http) throws Exception {
// 允许跨域访问
http.cors();
}
默认会找name为corsConfigurationSource的bean
@Bean
pubilc CorsConfigurationSource CorsConfigurationSource() {
CorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); //同源配置,*表示任何请求都视为同源,若需指定ip和端口可以改为如“localhost:8080”,多个以“,”分隔;
corsConfiguration.addAllowedHeader("*");//header,允许哪些header,本案中使用的是token,此处可将*替换为token;
corsConfiguration.addAllowedMethod("*"); //允许的请求方法,PSOT、GET等
((UrlBasedCorsConfigurationSource) source).registerCorsConfiguration("/**",corsConfiguration); //配置允许跨域访问的url
return source;
}
方式2
通过过滤器全局配置跨域
但是需要确保CORSFilter 的位置要在 spring security 验证用户密码的Filter 之前,这样才能在请求投中添加到跨域相关的字段,是预检请求通过,然后发送正式请求。
http.addFilterBefore(customCorsFilter, UsernamePasswordAuthenticationFilter.class);
customCorsFilter为过滤器,具体实现方式如下:
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config =new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送Cookie信息
config.setAllowCredentials(true);
//放行哪些原始域(请求方式)
config.addAllowedMethod("*");
//放行哪些原始域(头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
config.addExposedHeader("*");
//2.添加映射路径
UrlBasedCorsConfigurationSource configSource =new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}