1.产生跨域的原因
首先我们先来看看产生跨域的原因:
- 浏览器的限制--跨域问题不是前台或者后台的限制,而是浏览器出于安全考虑的限制。
- 请求跨域--协议、域名、端口发生变化
- 请求的type是XHR(XMLHttpRequest)
当同时满足这三个条件的时候才会产生跨域问题,所以解决跨域问题,我们可以从这三个方面出发。
这里我们主要介绍通过CORS来解决跨域问题
2.什么是CORS
简单来说,CORS是一种访问机制,英文全称是Cross-Origin Resource Sharing,即我们常说的跨域资源共享,通过在服务器端设置响应头,把发起跨域的原始域名添加到Access-Control-Allow-Origin 即可。
首先我们先来看一个有跨域问题的请求,你会发现他的请求头有一个Origin,发起跨域请求的原始域。
我们再看一下控制台的输出,它提示请求头里面没有Access-Control-Allow-Origin,导致这个请求被浏览器阻止了。
所以,我们解决跨域问题的方法就是在请求头添加Access-Control-Allow-Origin(表示允许哪些原始域进行跨域访问),这就是CORS解决跨域问题的原理。
3.CORS实现跨域访问
- 方式1:返回新的CorsFilter --针对全局
- 方式2:重写WebMvcConfigurer --针对全局
- 方式3:使用注解(@CrossOrigin) -- 针对局部,推荐使用
- 方式4:手工编写过滤器 -- 不依赖于框架,适用于低版本,不支持cors的情况
方式1--返回新的CorsFilter
在任意配置类,返回一个新的CorsFilter Bean,并添加映射路径和具体的CORS配置信息。
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 放行哪些原始域
config.addAllowedOrigin("*");
// 设置预检请求的有效期
config.setMaxAge(Long.valueOf(1800));
// 是否发送Cookie信息
config.setAllowCredentials(true);
// 放行哪些原始域(请求方式)
config.addAllowedMethod("*");
// 放行哪些原始域(头部信息)
config.addAllowedHeader("*");
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
//config.addExposedHeader("*");
//2.添加映射路径
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
方式2--重写WebMvcConfigurer
在任意配置类,返回一个新的WebMvcConfigurer Bean,并重写其提供的跨域请求处理的接口,目的是添加映射路径和具体的CORS配置信息。
@Configuration
public class GlobalCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
registry.addMapping("/**")
// 放行哪些原始域
.allowedOrigins("*")
// 是否发送Cookie信息
.allowCredentials(true)
// 放行哪些原始域(头部信息)
.allowedHeaders("*")
// 放行哪些原始域(请求方式)
.allowedMethods("*")
// 设置预检请求的有效期
.maxAge(1800);
}
};
}
}
方式3--使用注解(@CrossOrigin)
这个注解既可以针对类,也可以针对方法,有很好的灵活性,而且操作比较简单,比较推荐这个方法
@Slf4j
@RestController
@CrossOrigin(maxAge = 1800)
@RequestMapping("/test")
public class TestController {
@PostMapping("/postJson")
@CrossOrigin(origins = "http://localhost:8081")
public ServerResponse<String> postJson(@RequestBody User user) {
log.info("postJson");
return ServerResponse.createBySuccess(user.getName());
}
}
方式4--手工编写过滤器
对于不支持cors的版本,推荐自己编写一个过滤器。
package com.wyq.ajaxserver.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class MyCorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("跨域请求过滤器");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String origin = request.getHeader("Origin");
if (!StringUtils.isEmpty(origin)) {
// 放行哪些原始域
response.addHeader("Access-Control-Allow-Origin", origin);
}
// 是否发送Cookie信息
response.addHeader("Access-Control-Allow-Credentials", "true");
// 放行哪些原始域(头部信息)
response.addHeader("Access-Control-Allow-Headers", "*");
// 放行哪些原始域(请求方式)
response.addHeader("Access-Control-Allow-Methods", "*");
// 设置预检请求的有效期
response.addHeader("Access-Control-Max-Age", "1800");
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
@Configuration
public class GlobalCorsConfig {
@Bean
public MyCorsFilter myCorsFilter() {
return new MyCorsFilter();
}
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean<MyCorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.addUrlPatterns("/*");
registrationBean.setFilter(myCorsFilter());
registrationBean.setOrder(0);
return registrationBean;
}
}
测试结果
通过上面的方式,跨域问题得到了解决,但是有个很奇怪的现象,第一次发起了两次请求,第一条请求Method是OPTIONS请求,第二条请求Method才是实际发出的POST请求,这是为什么呢!
第一个OPTIONS的请求是由Web服务器处理跨域访问引发的,OPTIONS是一种“预检请求”。
浏览器在处理跨域访问的请求时如果判断请求为复杂请求,则会先向服务器发送一条预检请求,根据服务器返回的内容浏览器判断服务器是否允许该请求访问。
如果web服务器采用cors的方式支持跨域访问,在处理复杂请求时这个预检请求是不可避免的。
但是,我们可以通过设置Access-Control-Max-Age来设置预检请求的最大缓存时间,这样下次请求的时候,如果在缓存有效期内,就不会发起预检请求了。
参考文档: