运行环境
操作系统:Windows 10 ;
开发工具:IDEA-2019.3;
Web服务器:Tomcat 9.0.24;
JDK版本: jdk 1.8.0_221;
Spring boot版本:2.0.9.RELEASE
开始
想起最早搭建spring boot框架的时候,就遇到了跨域的问题。当时,也尝试去解决,虽然,最终解决了,但是,总是感觉稀里糊涂的。
这次看了公众号的文章《Cors跨域(一):深入理解跨域请求概念及其根因》,又燃起了我尝试重新梳理跨域问题的想法。
若想实现Cors机制的跨域请求,是需要浏览器和服务器同时支持的。关于浏览器对CORS的支持情况:现在都2021年了,so可以认为100%的浏览器都是支持的,再加上CORS的整个过程都由浏览器自动完成,前端无需做任何设置,所以前端工程师的ajax原来怎么用现在还是怎么用,它对前段开发人员是完全透明的。
我摘录了文章的一段话,从上面可以看出来,B/S架构下的项目,前端我们不做任何修改,我们主要解决的后端的问题。而后端的处理的目的也是为了让前端知道后端是如何允许跨域请求的。
方法1. 使用Filter过滤器解决
这里不依赖spring boot。主要思路就是开发一个Filter,并让Spring容器去扫描注册。
@Component
//filter虽然是servlet的三大组件之一,但是也是需要显式的注册在xml中的,后续servlet3.0后支持注解配置,使用@componet注解,
//让spring 去扫描filter,并注册使用
public class CORSFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "content-type,Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
方法2:配置FilterRegistrationBean
注意FilterRegistrationBean是spring boot框架提供的一个类。这个类为我们注册filter提供了便捷。我们要注册的也是spring-web内部提供的CorsFilter。
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
//跨域相关配置
Map<String, CorsConfiguration> corsConfigs = new LinkedHashMap<>();
corsConfigs.put("*", new CorsConfiguration().applyPermitDefaultValues());
corsConfigs.put("/**", new CorsConfiguration().applyPermitDefaultValues());
//组装跨域ConfigurationSource
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.setCorsConfigurations(corsConfigs);
//组装CorsFilter
CorsFilter corsFilter = new CorsFilter(configSource);
//注册filter
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(corsFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setName("corsFilter");
registrationBean.setOrder(1);
return registrationBean;
}
}
方法3:使用@CrossOrigin
使用@CrossOrigin,能够更加细粒度的控制跨域请求。
@CrossOrigin
@RequestMapping("/home")
public String home(Model model) {
return "home";
}
方法4:使用WebMvcConfigurer方式全局配置
使用spring mvc 提供的接口WebMvcConfigurer配置跨域。
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
//该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
//该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
.allowedHeaders("Access-Control-Allow-Origin","token","secret")
//CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
//.exposedHeaders("token","secret")
.allowCredentials(true)//它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中
//该字段可选,用来指定本次预检请求的有效期,单位为秒
.maxAge(3600);
}
}
上面4中方法,第一种用了servlet的知识,第二种用了spring boot的知识,第三种用了spring框架的细粒度控制注解,第四种是spring框架提供专门跨域解决方案。
第一种最好理解,第四种是最优雅解决方案。
总结
在解决这个问题的时候,由于涉及的知识块很多。会遇到的宏观问题有:
- 1.你不知道这是属于spring框架的,spring boot的,还是servlet的。
- 2.这是几个知识块组合才能解决的。
- 3.可以从任意一个知识块的角度出发都可以解决这个问题,条条大路通罗马。
你在解决这个问题之前,这3个问题始终都会缠绕在一起,让人秃头。等回头看,这3个问题都有三个正确的答案。但是,我们又浪费了很多的时间。
我从别人身上学到一个优点,就是,以解决问题为导向。最重要的,是先要解决问题,不要管解决的方案是不是很啰嗦,是不是不优雅。只有解决了问题,才能回过头剖析问题,优化方案。