最近遇到一个跨域问题,弄了一段时间,所以记录下
简单的GET 请求之类的,走jsonp就好了,但是这次涉及到POST,所以就弄了CORS。参看 这篇Cors跨域(一):深入理解跨域请求概念及其根因,大致了解了其实本质上就是要在返回的header时候加上几个,类似Access-Control-Allow-Origin 等等。
于是尝试在返回的header里加上了几个 header。但是奇怪的事情发生了。get请求跨域正常,同事写的POST也正常,偏偏是我写的 post 接口异常。
代码如下
@RequestMapping(value = "home/applySubmission.do", method = {RequestMethod.POST, RequestMethod.OPTIONS})
@ResponseBody
public Map<String, Object> applySubmission(HttpServletRequest request, @RequestBody Map<String, Object> param) {
//业务逻辑
}
后来才发现是因为 我发的跨域不是简单请求 ,简单请求定义
何为简单请求
Cors规范定义简单请求的原则是:请求不是以更新(添加、修改和删除)资源为目的,服务端对请求的处理不会导致自身维护资源的改变。对于简单跨域资源请求来说,浏览器将两个步骤(取得授权和获取资源)合二为一,由于不涉及到资源的改变,所以不会带来任何副作用。
对于一个请求,必须同时符合如下要求才被划为简单请求:
Http Method只能为其一:
GET
POST
HEAD
请求头只能在如下范围:
Accept
Accept-Language
Content-Language
Content-Type,其中它的值必须如下其一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
除此之外的请求都为非简单请求(也可称为复杂请求)。非简单请求可能对服务端资源改变,因此Cors规定浏览器在发出此类请求之前必须有一个“预检(Preflight)”机制,这也就是我们熟悉的OPTIONS请求。
而我同事恰好 是 application/x-www-form-urlencoded ,我是json 那肯定是复杂的请求,需要preflight机制 ,提前发一个options 请求。搜了一圈 ,网上的套路大部分都是springboot,一看我们的项目 是spring 3.x 。好像没有现成的轮子。后来 大概看了两种思路
如果走拦截器,那必然会要走 Dispatchservlet 中
首先必须要配置这个 ,要让框架处理 option 请求
<servlet>
<servlet-name>xxx</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/spring/spring-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
否则在FrameworkServlet 由于 dispatchOptionsRequest 默认false ,好像走了父类方法,就不会走你写的 拦截器
/**
* Delegate OPTIONS requests to {@link #processRequest}, if desired.
* Applies HttpServlet's standard OPTIONS processing otherwise,
* and also if there is still no 'Allow' header set after dispatching.
* @see #doService
*/
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchOptionsRequest) {
processRequest(request, response);
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
super.doOptions(request, response);
}
这里再补充一点
方法上的 method = {RequestMethod.POST, RequestMethod.OPTIONS},一定要加上 RequestMethod.OPTIONS
,否则会报handler 找不到 方法 ,都不会再到 拦截链那里!!
最后拦截器写法
public class CorsFilter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if(request.getMethod().equals(RequestMethod.OPTIONS.name())) {
response.setStatus(HttpStatus.OK.value());
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With");
response.setHeader("Access-Control-Max-Age", "86400");
response.setHeader("Allow", "true");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}