大背景:前后端分离后,项目分开部署,域名不一致,ajax请求时需要解决跨域问题。
服务端支持跨域方案:
1、spring早已经支持跨域的配置。
2、@CrossOrgin注解方式,支持配置到controller或者具体的方法上。不多解释注解的参数。
说明:我理解的是,1和2方案都是通过spring提供的CorsFilter做了拦截。
3、spring 拦截器方式
public classCrossDomainInterceptorextendsHandlerInterceptorAdapter {
@Override
public booleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException {
if(RequestMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
response.addHeader("Access-Control-Allow-Methods","GET,POST,PUT,OPTIONS,DELETE");
response.addHeader("Access-Control-Max-Age","1800");
response.addHeader("Access-Control-Allow-Headers","Content-Type,x-requested-with,access-token");
response.addHeader("Access-Control-Allow-Credentials","true");
//response.setStatus(HttpStatus.SC_OK);
return false;
}
return true;
}
}
4、自定义Filter,然后在XML配置 filter。
public classCrossDomainFilterimplementsFilter{
@Override
public voidinit(FilterConfig filterConfig)throwsServletException {
}
@Override
public voiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException {
if(servletRequestinstanceofHttpServletRequest && servletResponseinstanceofHttpServletResponse) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if(CorsUtils.isCorsRequest(request)) {
log.info(request.getRequestURL().toString() +" options request into CrossDomainFilter");
if(!processRequest(request,response) || CorsUtils.isPreFlightRequest(request)) {
return;
}
}
filterChain.doFilter(request,response);
}else{
filterChain.doFilter(servletRequest,servletResponse);
}
}
private booleanprocessRequest(HttpServletRequest request,HttpServletResponse response) {
booleanisValid =false;
String curOrigin = request.getHeader("Origin");
String[] domains ="http://localhost:8080,http://localhost:8090".split(",");
if(domains.length>0) {
for(String domain : domains) {
if(curOrigin.equals(domain)) {
response.addHeader("Access-Control-Allow-Origin",domain);
response.addHeader("Access-Control-Allow-Methods","GET,POST,PUT,OPTIONS,DELETE");
response.addHeader("Access-Control-Allow-Headers","Content-Type, x-requested-with, access-token");
response.addHeader("Access-Control-Max-Age","1800");
response.addHeader("Access-Control-Allow-Credentials","true");
isValid =true;
break;
}
}
}
returnisValid;
}
@Override
public voiddestroy() {
}
}
以上四种方案好像所以的看上去都是那么简单,没什么难度。我一开始也是这样认为的。然而。。。。
过程中遇到如下几个问题。
1)我们希望domain可支持的域名是可以动态配置,通过公司的配置系统可以动态获取allowOrigins。那么方案1被我抛弃了,因为xml里配置无法动态生效。因为是基于全局配置,就没有考虑方案2.
2)我用了spring拦截器方式CrossDomainInterceptor,或者CrossDomainFilter extends OncePerRequestFilter。就是这个过程都被spring管理着。但是出现了状况:
A、请求依然403. 原因:经过各种尝试发现,Access-Control-Allow-Credentials、Access-Control-Allow-Headers有设置时,Access-Control-Allow-Origin 不要设置成 *。并且Access-Control-Allow-Headers 需要和前端传上来的header 参数名匹配上,否则很容易403.
B、解决了测试环境的403之后,有同学又反应本地开发环境出现“Invalid Cors Request”。找了4个同学本地postman请求服务端接口,2个同学反应没有问题,2个同学反应出现“Invalid Cors Request”。这里很难理解,postman里的请求就是简单的接口测试,不存在跨域问题,无法理解。解决方案:在网上有看到帖子说是因为多个filter顺序问题,其他filter导致了跨域filter出现了问题。。。(确实项目还集成了其他需要用到过滤器的地方,如:安全框架接入)
基于以上出现的状况,我决定从头开始,回归本质。首先跨域就是需要对浏览器PreflightRequest(Options预检查请求)。进行过滤。那么就定义一个java filter吧。filter不再继承OncePerRequestFilter(毕竟让spring控制后自己就不会关注那么多了,这未必是好事)。然后把filter配置到web.xml(可以决定filter的顺序,也为了解决以上最后提到的filter顺序问题)。代码参考方案4。结果嘛,自然是一切就那么好起来了。
Access-Control-Allow-Headers 需要支持前端传上来的参数。
Access-Control-Allow-Credentials、Access-Control-Allow-Headers有设置时,Access-Control-Allow-Origin 不要设置成 *,只能支持一个。
注意过滤器的顺序问题。(即,你需要先做哪一步过滤)
引用一些参考的文章:
https://blog.csdn.net/dalangzhonghangxing/article/details/52911230
https://blog.csdn.net/pinebud55/article/details/60874725
https://segmentfault.com/a/1190000012469713