cors简介
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出跨域检测请求,从而克服了AJAX只能同源使用的限制。
明确:跨域请求不允许,不是服务器的限制,是浏览器的安全限制。
所以,服务端如果要实现跨域支持的关键就是对前端发出的跨域检测请求作出对应的应答。整体思路如下:
如果服务端要实现对跨域请求的支持,需要:
- 在前端发出跨域的
OPTIONS
请求时,根据OPTIONS
请求header中得到的Access-Control-Request-
参数,返回时在header中带上对应的Access-control-allow-..
。 - 在所有和前端交互的请求的返回中,都加上对应的header
CORS跨域实现方法解析
只要能根据逻辑指定入参返回对应的出参,都能实现跨域功能。所以实现跨域功能我见过的,有以下几种方法:
- 使用 Spring MVC 提供的跨域配置进行支持
- 依赖 Servlet 的过滤器的链式调用,实现对跨域请求的拦截和处理
- 依赖 Spring 的拦截器,实现对跨域请求的处理
- 如果在你的服务器前面有一层 nginx 代理,可以依赖 nginx 完成对跨域的支持
接下来依次记录。
使用 Spring MVC 配置跨域
Spring MVC 直接提供了对跨域的支持,可以直接进行配置。
Spring MVC + Java配置
直接覆盖
Application
直接继承WebMvcConfigurerAdapter
,然后在Application
中直接复写所需方法
public class Application extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("*");
}
}
创建配置Bean
在某个@Component
中直接返回一个WebMvcConfigurerAdapter
的bean
@Configuration
public class CORSConfiguration {
public CORSConfiguration() {
}
@Bean
public WebMvcConfigurer devCorsConfigurer() {
return new WebMvcConfigurerAdapter() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("*");
};
}
}
}
Spring MVC + XML 配置
<!-- CORS配置 -->
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="*"
allowed-methods="*"
allowed-headers="*"
allow-credentials="true"
max-age="3600"/>
</mvc:cors>
注意,如果需要cors
标签,命名空间需要写成http://www.springframework.org/schema/mvc/spring-mvc.xsd
,xsd文件不要精确到版本编号,否则在校验 xml 时会失败。
使用过滤器配置跨域
使用 SpringBoot 的 CORS 过滤器
配置代码如下:
@Bean
public FilterRegistrationBean<Filter> corsFilter(SwordCorsProperties properties) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(properties.getPath(), properties.getConfiguration());
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
其中,入参为跨域要支持的域名、方法等配置,是对 SpringBoot配置的定制:
/**
* 注意了,那个AllowCredentials最好配置成 true
**/
@ConfigurationProperties(prefix = "sword.cors.filter")
public class SwordCorsProperties {
private CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
private String path = "/api/**";
public CorsConfiguration getConfiguration() {
return configuration;
}
public void setConfiguration(CorsConfiguration configuration) {
this.configuration = configuration;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
根据跨域规则定制过滤器
自行实现javax.servlet.Filter
接口,实现对跨域请求的拦截和返回。并在 web.xml 中配置使其生效:
<filter>
<filter-name>C</filter-name>
<filter-class>你写的拦截器的类</filter-class>
<init-param>
<param-name>C_input</param-name>
<param-value>C_input</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>C</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用拦截器配置跨域
自行实现org.springframework.web.servlet.HandlerInterceptor
接口,实现对跨域请求的拦截和返回。并在 Spring-mvc.xml 中配置使其生效:
<!--拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/api/**"/>
<bean id="XXX" class="你写的拦截器的类"/>
</mvc:interceptor>
</mvc:interceptors>
使用 nginx 配置跨域
这个方法是我在总结时别人给我随口说的,去网上看了一下,发现是可以的。既然只是要在OPTIONS
请求结果中添加对应的header
,那么在nginx
层完全可以做到:
location / {
# 检查域名后缀
if ($http_origin ~ \.test\.com) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
add_header Access-Control-Max-Age 1728000;
}
# options请求不转给后端,直接返回204
# 第二个if会导致上面的add_header无效,这是nginx的问题,这里直接重复执行下
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type;
add_header Access-Control-Max-Age 1728000;
return 204;
}
# 其他请求代理到后端
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://xxx.xxx.xxx.xxx;
}
以上配置实现了:
- 对指定域名的跨域支持
- 对其他OPTION跨域请求的相应
还有做的比较粗糙的,统一在所有返回结果都增加对应的header:
# 直接请求nginx也是会报跨域错误的这里设置允许跨域
# 如果代理地址已经允许跨域则不需要这些, 否则报错(虽然这样nginx跨域就没意义了)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials true;
add_header
可以配置在http,server,location
中,但是有覆盖关系——仅最近一处的add_header
起作用。http
、server
和location
三处均可配置add_header
,但起作用的是最接近的配置,往上的配置都会失效。
文献
springboot服务端配置:
https://blog.csdn.net/qq779446849/article/details/53102925
nginx支持跨域:
https://segmentfault.com/a/1190000012028144
https://blog.csdn.net/m0_37886429/article/details/83618506
Nginx add_header 注意点: