CORS 几种解决方案
来源:互联网 发布:淘宝怎么看自己评价的 编辑:程序博客网 时间:2024/05/17 07:44
CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否.
Access-Control-Allow-Origin:指定授权访问的域
Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)
一:简单的自定义CORSFilter / Interceptor
适合设置单一的(或全部)授权访问域,所有配置都是固定的,特简单。也没根据请求的类型做不同的处理
在web.xml 中添加filter
<filter> <filter-name>cros</filter-name> <filter-class>cn.ifengkou.test.filter.CORSFilter</filter-class></filter><filter-mapping> <filter-name>cros</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
新增CORSFilter 类
@Componentpublic class CORSFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); response.addHeader("Access-Control-Allow-Headers", "Content-Type"); response.addHeader("Access-Control-Max-Age", "1800");//30 min filterChain.doFilter(request, response); }}
Access-Control-Allow-Origin只能配置 或者一个域名*
比如配置了192.168.56.130,那么只有192.168.56.130 能拿到数据,否则全部报403异常
response.addHeader("Access-Control-Allow-Origin", "http://192.168.56.130");
二:Nginx 配置支持Ajax跨域
这里是一个nginx启用COSR的参考配置:来源
## Wide-open CORS config for nginx#location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # # Custom headers and headers various browsers *should* be OK with but aren't # add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; # # Tell client that this pre-flight info is valid for 20 days # add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; }}
三:支持多域名配置的CORS Filter
因为知道已经有可以用的库可以解决,所以就没重复造轮子了。其实因为懒,看看别人的源码算了。。。
在mvnrepository搜索cors-filter,目前也就两个可以用
- org.ebaysf.web 的 cors-filter,项目地址:https://github.com/ebay/cors-filter
- com.thetransactioncompany的 cors-filter,项目地址:http://software.dzhuvinov.com/cors-filter.html
这两个也都大同小异,因为ebay开源在github上,也有详细的README,那么就以ebay的cors-filter为例
配置
添加依赖包到项目:
<dependency> <groupId>org.ebaysf.web</groupId> <artifactId>cors-filter</artifactId> <version>1.0.1</version></dependency>
添加配置(具体配置项,还是见项目的README.md吧)
<filter> <filter-name>CORS Filter</filter-name> <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>http://192.168.56.129,http://192.168.56.130</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
源码分析
源码地址:github。但通过IDEA Decompiled 出来的更清晰.....,以下是反编译的
ebaysf的cors-filter 只有一个类CORSFilter。也就是一个拦截器,implements Filter
public final class CORSFilter implements Filter {
通过是实现Filter 的init 方法从配置文件中读取参数:
public void init(FilterConfig filterConfig) throws ServletException { this.parseAndStore("*", "GET,POST,HEAD,OPTIONS", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers", "", "true", "1800", "false", "true"); this.filterConfig = filterConfig; this.loggingEnabled = false; if(filterConfig != null) { String configAllowedOrigins = filterConfig.getInitParameter("cors.allowed.origins"); String configAllowedHttpMethods = filterConfig.getInitParameter("cors.allowed.methods"); String configAllowedHttpHeaders = filterConfig.getInitParameter("cors.allowed.headers"); String configExposedHeaders = filterConfig.getInitParameter("cors.exposed.headers"); String configSupportsCredentials = filterConfig.getInitParameter("cors.support.credentials"); String configPreflightMaxAge = filterConfig.getInitParameter("cors.preflight.maxage"); String configLoggingEnabled = filterConfig.getInitParameter("cors.logging.enabled"); String configDecorateRequest = filterConfig.getInitParameter("cors.request.decorate"); this.parseAndStore(configAllowedOrigins, configAllowedHttpMethods, configAllowedHttpHeaders, configExposedHeaders, configSupportsCredentials, configPreflightMaxAge, configLoggingEnabled, configDecorateRequest); }}
parseAndStore 方法,解析参数。以 解析cors.allowed.orgins为例;其他参数同理
Set e; if(allowedOrigins != null) { if(allowedOrigins.trim().equals("*")) { this.anyOriginAllowed = true; } else { this.anyOriginAllowed = false; e = this.parseStringToSet(allowedOrigins); this.allowedOrigins.clear(); this.allowedOrigins.addAll(e); } }//parseStringToSet//对多域名用点分割,加到HashSet中,再赋给allowedOrigins(Collection<String> allowedOrigins = new HashSet();)private Set<String> parseStringToSet(String data) { String[] splits; if(data != null && data.length() > 0) { splits = data.split(","); } else { splits = new String[0]; } HashSet set = new HashSet(); if(splits.length > 0) { String[] arr$ = splits; int len$ = splits.length; for(int i$ = 0; i$ < len$; ++i$) { String split = arr$[i$]; set.add(split.trim()); } } return set;}
如何实现 doFilter
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if(servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) { HttpServletRequest request1 = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; //识别request 属于哪种类别 CORSFilter.CORSRequestType requestType = this.checkRequestType(request1); if(this.decorateRequest) { decorateCORSProperties(request1, requestType); } switch(CORSFilter.SyntheticClass_1.$SwitchMap$org$ebaysf$web$cors$CORSFilter$CORSRequestType[requestType.ordinal()]) { case 1: this.handleSimpleCORS(request1, response, filterChain); break; case 2: this.handleSimpleCORS(request1, response, filterChain); break; case 3: this.handlePreflightCORS(request1, response, filterChain); break; case 4: this.handleNonCORS(request1, response, filterChain); break; default: this.handleInvalidCORS(request1, response, filterChain); } } else { String request = "CORS doesn\'t support non-HTTP request or response."; throw new ServletException(request); }}
判断request类别,根据类别进行差异化处理。handleSimpleCORS 处理过程,判断是否设置允许所有origin参数,判断是否符合httpMethods要求,判断此次request的origin(origin = request.getHeader("Origin"))是否在allowedOrigins(origin白名单)内。如果在,就设置response.addHeader("Access-Control-Allow-Origin", origin);这样也就实现了多域名支持。流程图就不画了...
public void handleSimpleCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { CORSFilter.CORSRequestType requestType = this.checkRequestType(request); String origin; if(requestType != CORSFilter.CORSRequestType.SIMPLE && requestType != CORSFilter.CORSRequestType.ACTUAL) { origin = "Expects a HttpServletRequest object of type " + CORSFilter.CORSRequestType.SIMPLE + " or " + CORSFilter.CORSRequestType.ACTUAL; throw new IllegalArgumentException(origin); } else { origin = request.getHeader("Origin"); String method = request.getMethod(); if(!this.isOriginAllowed(origin)) { this.handleInvalidCORS(request, response, filterChain); } else if(!this.allowedHttpMethods.contains(method)) { this.handleInvalidCORS(request, response, filterChain); } else { if(this.anyOriginAllowed && !this.supportsCredentials) { response.addHeader("Access-Control-Allow-Origin", "*"); } else { response.addHeader("Access-Control-Allow-Origin", origin); } if(this.supportsCredentials) { response.addHeader("Access-Control-Allow-Credentials", "true"); } if(this.exposedHeaders != null && this.exposedHeaders.size() > 0) { String exposedHeadersString = join(this.exposedHeaders, ","); response.addHeader("Access-Control-Expose-Headers", exposedHeadersString); } filterChain.doFilter(request, response); } } }
为了避免对参数一知半解,就把作者的参数描述表贴上来,通过参数表可以了解下header里面各个参数的作用
测试:
1.服务端准备接口(我的地址是:http://192.168.10.61:8080/api)
@RequestMapping(method = RequestMethod.GET,value = "test")@ResponseBodypublic HashMap<String,Object> getArticles(){ HashMap<String,Object> map = new HashMap<>(); map.put("result","success"); return map;}
2.过滤器配置(web.xml),配置允许访问的域为:http://192.168.56.129,http://www.website2.com
<filter> <filter-name>CORS Filter</filter-name> <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>http://192.168.56.129,http://www.website2.com</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3.准备测试网页index.html:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>cors test page</title> <script src="jquery.min.js"></script> <script> function loadData(){ $.ajax({ url: "http://192.168.10.61:8080/api", type:"GET", dataType:"json", timeout:10000, success:function(data){ $("#result").append(data.result+"<br />"); console.log(data); }, error:function(e){ $("#result").append(e.statusText+"<br />"); } }); } $(function(){ $("#host").append("origin:"+window.location.origin); }); </script></head><body><button onclick="loadData()">onclick</button><div id="host"></div><div id="result" style="height:200px;width:100%"></div></body></html>
4.将index.html发布到nginx(nginx后面也有方案)
index.html 不能直接用浏览器打开运行,虽然可以调用Ajax请求,但是域是file:///path/index.html
虚拟机增加一个网卡地址(原机器IP是192.168.56.129)
ifconfig eth0:0 192.168.56.130
建立两个测试网站
cd homemkdir /website1 #站点目录mkdir /website2
将index.html 传输到这两个目录
配置nginx,增加两个server节点
# ----server1 ----server { listen 192.168.56.129:80; server_name www.website1.com; location / { root /website1; index index.html index.htm; }}# ----server2 ----server { listen 192.168.56.130:80; server_name www.website2.com; location / { root /website2; index index.html index.htm; }}
重启nginx服务
./nginx -s reload
5.修改本地hosts文件
//hosts文件路径:windows系统一般在C:\Windows\System32\drivers\etc192.168.56.129 www.website1.com192.168.56.130 www.website2.com
通过增加虚拟网卡 、nginx代理 和 修改hosts文件,我在本地就有4个网站(域)可以进行测试了,分别是:
- http://192.168.56.129
- http://192.168.56.130
- http://www.website1.com
- http://www.website2.com
6.测试
准备:
(chrome)打开4个tab,分别进入到上述四个网站,页面打印了当前origin,通过onclick调用Ajax请求,页面布局如下
预期:
- http://192.168.56.129 SUCCESS
- http://192.168.56.130 ERROR
- http://www.website1.com ERROR
- http://www.website2.com SUCCESS
结果:
符合预期!
建议使用,除了对域的过滤,还做了其他很多操作,比简单的自定义过滤器考虑得周全,例如
this.handlePreflightCORS(request1, response, filterChain);this.handleNonCORS(request1, response, filterChain);this.handleInvalidCORS(request1, response, filterChain);
总结
cors在开发WebService、RESTful API 时经常会遇到,在以前可能直接通过jsonp解决,jsonp怎样怎样就不多说了。 总之,CORS技术规范出来这么久了,如果不考虑IE6 IE7的问题,那么还是积极拥抱CORS吧
上文三种解决方案,通过搜索引擎均能找到,但估计大部分都是用的第一种最简单的无脑的Cors Filter处理,第二种方案是通过nginx配置的,并不适合所有Web应用。第三种,考虑得很周全,而且使用方便,如果不考虑造重复轮子,推荐使用。
本文所用的测试工程代码太简单了,就不放github了,直接下载吧,项目下载地址
断断续续写了好几天,转载请附带原文路径:http://www.cnblogs.com/sloong/p/cors.html
- CORS 几种解决方案
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- ajax 跨域----post解决方案CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- +++AJAX POST&跨域 解决方案 - CORS+++
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- AJAX POST&跨域 解决方案 - CORS
- cors java跨域解决方案
- PHP的数据类型
- [iOS]多国语言国际化
- 在新建的Qt项目中添加显示点云的部件
- The Unique MST(次小生成树)
- js获得近六个月的时间
- CORS 几种解决方案
- jQuery--动画篇(二)
- ajax jsonp的跨域请求
- 牛客网笔试输入的问题(Java)
- 滴滴出行2018内推编程题
- Sublime Text 3 import Anaconda 无法正常补全模块名解决办法
- 面试题 31:连续子数组的最大和(滴滴的“连续最大和”)
- Logistics Rrgression(Logistics回归)
- JAVA 反射机制(Java Reflection)总结(二)