参考:http://restlet.com/company/blog/2015/12/15/understanding-and-using-cors/
https://stackoverflow.com/questions/27915191/how-does-the-chrome-browser-decide-when-to-send-options
什么是CORS
Cross Origin Resource Sharing(CORS),顾名思义,即跨域共享,当两个不同domain进行访问的时候,默认情况是不能访问的,需要解决CORS问题。
理解CORS两种不同请求
在远程web应用决定一个request是否能被服务的时候,CORS会区分两种请求:
(1)Simple requests :如果远程服务器允许当前发送域发送请求,简单请求就会直接返回结果,否则会返回权限错误
(2)Preflighted requests:先发送一个和target request一样的url的OPTIONS请求去检查是否有权利执行这个请求,OPTIONS如果正常处理了就会返回什么类型的请求可以被执行,浏览器就会匹配当前请求,如果可以被执行,就会接着发送真正的target请求。举例:
当我们向请求头添加signkey、appid、token等字段的时候,浏览器首先发送一个option请求,然后根据服务器的设置请求返回如下字样:
可以看到允许header的内容包括signkey、appid、token等,所以浏览器就第二次发送请求:
这样Preflighted请求就正常发送了
两种请求发送时机
如果满足下面的情况,跨域算法就会发送简单请求:
- request method是simple method 并且没有设置force preflight flag(这个怎么设置暂时还没找到)
- request header是simple header或者request header为空
否则,就会发送preflight request
simple method以及simple header
1、simple method(区分大小写)
- GET
- HEAD
- POST
2、simple header(区分大小写)
- header字段包括Accept, Accept-Language, 或者 Content-Language
- header字段包括Content-Type并且Content-Type值为 application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain.
常见服务器处理跨域问题
各个平台跨域链接:https://enable-cors.org/
没有仔细看,就试了几个:
(1)springMVC跨域:参见http://blog.youkuaiyun.com/isea533/article/details/50449907
(2)springBoot跨域:
添加一个配置类,继承WebMvcConfigurerAdapter
重写addCorsMappings方法:
@Configuration
public class CorsConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //允许跨域访问的链接 "/**" 表示允许所有链接
.allowedMethods("*"); //允许的http方法(GET,PUT,POST,DELETE...),"*"表示允许所有方法
}
}
(3)jersey跨域:http://www.codingpedia.org/ama/how-to-add-cors-support-on-the-server-side-in-java-with-jersey/
先写Provider类(我的方法名是CrosFliter有点问题,应该是CrosPorvider比较合理)
@Provider
public class CORSResponseFilter
implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
MultivaluedMap<String, Object> headers = MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT,OPTIONS");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, X-Codingpedia,signkey,appid,token");
}
}
然后注册这个Provider,常见注册方法见这个:https://blog.dejavu.sk/2013/11/19/registering-resources-and-providers-in-jersey-2/
我使用的是在web.xml注册:
<servlet>
<servlet-name>MobileJerseyServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.xx.xx.mobile.rest</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.scanning.recursive</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>com.xx.xx.filter.CrosFilter</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MobileJerseyServlet</servlet-name>
<url-pattern>/api/ams/mobile/*</url-pattern>
</servlet-mapping>
</servlet>
可以看到,在我的CORSResponseFilter类中,为了能接收OPTIONS请求,我在Access-Control-Allow-Methods加了OPTIONS,然后为了能够接收header有signkey、appid、token等特殊字段的请求,我也添加了允许。
最后,因为OPTIONS请求是不会携带一些特殊数据比如token信息的,所以如果你的服务器有对这个信息的校验的时候,需要忽略OPTIONS请求,否则OPTIONS请求被判为不合法:
if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
//过滤处理的代码
}
}else{
chain.doFilter(request,response);
}