最近把公司的一个整合了swagger2的rest工程的springmvc版本由3.2.16升级至4.3.19,测试过程中发现swagger-ui无法对接口进行测试了,从各方面查找原因并最终解决,写下此文记录过程。
确定为cors问题
这是最重要的一步,确定了方向之后的解决方案网上还是很多的。
现象如下:
- 测试发现使用REST Client可以成功调用接口,无论是业务接口还是swagger2的apiDocs接口(以下简称apiDocs接口);
- swagger-ui可以获取接口列表,但是调用业务接口时会失败;
- 调用业务接口(POST)时,发现先进行了一次同url的OPTIONS请求,且状态码为403。
根据第三点的信息,查询了关于OPTIONS请求的知识,进了解到了cors的信息,并以此为方向渐渐的把问题解决了。
说明:swagger-ui调用业务接口使用的是ajax,所以需要处理跨域问题,而RESTClient调用不涉及到跨域问题,所以可以正常调用。
跨过的坑
1. 使用自定义过滤器(失败)
项目本身有一个自定义的过滤器处理的CORS请求,代码如下(web.xml):
<filter>
<filter-name>cors</filter-name>
<filter-class>com.xxx.xxx.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/v2/api-docs</url-pattern>
</filter-mapping>
CorsFilter.java代码如下:
package com.xxx.xxx;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
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", "x-requested-with,Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
}
配合旧版本的springmvc食用没有问题,但是升级新版本springmvc后不好使了,但是并没有完全失效,那些走springmvc的业务接口无法调试,但是swagger2的apiDocs接口可正常调用,而且网上有文章说关于cors的解决方案就是加入这样的过滤器就可以了,导致我一度觉得不是cors的问题。
其实原因是虽然在过滤器里处理的response,但是新版springmvc在DispatcherServlet中也进行了判断,判断请求如果是跨域的则读取spring关于cors的配置,如果没有配置的话就会将response的状态码置为403,所以过滤器无效了。 我这次在追踪源代码的核心目标就是“找到哪一行代码将状态置为403”,直接从“祖坟”DispatcherServlet开始打断点一步步跟进,没费多少时间就找到了。
2. 使用spring提供的过滤器(失败)
网上有文章说可以通过配置spring提供的过滤器来解决,配置方式和上边web.xml中的代码相同,把filter-class改为org.springframework.web.filter.CorsFilter就可以了。但是在4.3.19版本中是不行的,因为CorsFilter这个类没有无参构造方法,启动项目会报错。参考的文章使用的是4.2版本,可能那个版本可以这么配置,不过我没有进行尝试。
3. 继承WebMvcConfigurerAdapter 类(失败)
代码如下
@Configuration
@EnableWebMvc
public class MyAdapter extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
无论是使用@Configuration和@EnableWebMvc还是使用@Component,还是在xml中配置bean,都无效…可能这是使用springboot才能生效的配置方式吧。
最终解决方案:在springmvc的配置文件中添加cors的相关配置
配置内容如下:
<mvc:cors>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="GET,POST,OPTIONS,DELETE"
allowed-headers="x-requested-with,Content-Type"
max-age="3600"/>
</mvc:cors>
其中allowed-origins默认就是"*",即所有其他域的请求都不拦截,此处可以不配,其他的可以参考.xsd文档,说明都很清楚;path是相对路径,不要加上servlet-mapping中的url-pattern,例如上方web.xml中配置了<url-pattern>/api/*</url-pattern>
,那么在这个path中就不要在前边加上/api了。
PS:如果项目的接口较少或者针对特定的接口有cors需求,可以使用@CrossOrigin
注解来处理,本文就不详细描述了。
本文参考了下方其他码友或组织的文章,特别感谢他们的分享:
- cors说明文档:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS - springmvc cors跨域实现源码解析:
https://www.cnblogs.com/leftthen/p/6378090.html - spring官方说明:
http://spring.io/guides/gs/rest-service-cors/
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework - 其他码友的文章:
https://blog.csdn.net/isea533/article/details/50449907
https://blog.csdn.net/lishirong/article/details/54946241