在前段时间的工程项目中,碰到这种两个情景:圣索弗合作开发H5网页并布署在圣索弗的伺服器上,但网页中内嵌了己方的SDK,SDK会间接初始化己方的USB,如下表所示图:
但控制面板中却会接到如下表所示收起:
Access to XMLHttpRequest at http://example1.com/test from origin http://example2.com has been blocked by CORS policy: Response to preflight request doesnt pass access controlcheck: No Access-Control-Allow-Originheaderis present on the requested resource.这是布吕马的收起。
布吕马是甚么
布吕马,是指应用程序无法继续执行其他中文网站的JAVA。它是由应用程序的相混思路导致的,是应用程序对javascript实施的安全可靠管制。
单纯来说,从门牌号A读取的网页,无法出访门牌号B的服务工程项目(示意图)。这时门牌号A与门牌号B不相混。
简而言之相混,是搜索引擎、协定、路由器均完全相同。举个范例:
http://www.123.com/index.html 初始化 http://www.123.com/abc.do (非布吕马) http://www.123.com/index.html 初始化 http://www.456.com/abc.do(主搜索引擎完全相同:123/456,布吕马) http://abc.123.com/index.html 初始化 http://def.123.com/server.do (子搜索引擎完全相同:abc/def,布吕马) http://www.123.com:8080/index.html 初始化 http://www.123.com:8081/server.do(路由器完全相同:8080/8081,布吕马) http://www.123.com/index.html 初始化 https://www.123.com/server.do (协定完全相同:http/https,布吕马)如上所述,由于圣索弗的搜索引擎与己方的搜索引擎完全相同,从圣索弗读取的网页,初始化己方USB的时候,就会出现布吕马的收起。
是否有办法可以化解这个问题呢,需要从CORS说起。
CORS
随着互联网的发展,相混思路严重影响了工程项目之间的连接,尤其是大工程项目,需要多个搜索引擎配合完成,因此W3C推出了CORS,
CORS需要应用程序和服务工程项目器同时支持,目前,所有应用程序都支持该功能。对于合作开发者来说,CORS通信与相混的AJAX通信没有区别,代码完全一样。应用程序在布吕马出访时,会自动添加HTTP头信息,或者发起预检请求,用户对此毫无感知。因此是否支持布吕马请求,关键在于伺服器是否做了CORS配置,允许布吕马出访。
应用程序将布吕马请求分为两类:单纯请求和非单纯请求。
同时满足以下两大条件的,就属于单纯请求:
请求方式是以下3种之一:GETPOSTHEADHTTP头信息不超出以下字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:仅限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain凡是不满足以上条件的,就属于非单纯请求。如我们常用的json格式请求,由于其Content-Type的值为application/json,因此属于非单纯请求。
对于这两种请求,应用程序的处理方式是不一样的。
单纯请求
对于单纯请求,应用程序采用先请求后判断的方式,即应用程序间接发出CORS请求,即在请求头中增加Origin字段,如图:
Origin字段用来向伺服器说明,本次请求来自于哪个源(协定+搜索引擎+路由器),伺服器决定是否允许这个源的出访。
伺服器判断该源如果不在自己允许的范围内,就返回两个正常的HTTP响应。应用程序判断响应头中是否包含Access-Control-Allow-Origin字段,如果没有,应用程序就知道伺服器是不允许布吕马出访的,就会抛出错误。
如果Origin在伺服器允许的范围内,伺服器的HTTP响应中,就会包含如下表所示字段:
Access-Control-Allow-Origin 它的值要么是请求时Origin字段的值,要么是两个*(表示接受任意搜索引擎的请求)。 Access-Control-Allow-Credentials它的值是两个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示伺服器明确许可,Cookie可以包含在请求中,一起发给伺服器。Access-Control-Allow-Headers 允许应用程序在CORS中发送的头信息。 Access-Control-Allow-Methods
允许应用程序在CORS中使用的方式。
应用程序收到伺服器返回的HTTP响应后,即可知道甚么样的CORS请求是被允许的。
非单纯请求
对于非单纯请求,应用程序采用预检请求,询问伺服器是否支持布吕马请求。在正式的请求之前,应用程序会预先发送两个额外的OPTIONS请求,询问伺服器当前网页所在的搜索引擎是否在伺服器的许可名单之中,以及可以使用哪些HTTP方式和头字段。只有得到肯定答复,应用程序才会发出正式的XMLHttpRequest请求,否则就收起。如图:
HTTP正式请求的方式是POST,并且发送两个头信息content-type(本例中使用content-type=application/json,因此是非单纯请求)。
伺服器接到预检请求之后,检查Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,并做出响应,如下表所示图:
Access-Control-Max-Age
用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是3600秒,即允许缓存该条回应3600秒,在此期间,可间接发送正式请求,不用再发预检请求。
在上图例中,应用程序请求Origin是http://192.168.47.130,伺服器响应Access-Control-Allow-Origin是http://127.0.0.1,因此应用程序会收起。只有在伺服器响应与应用程序的请求内容相匹配,应用程序才不收起。
跨域的化解办法
碰到布吕马的收起,可以分别从客户端和服务工程项目端去化解。
客户端
通过上面的分析可以知道,布吕马的判断是在应用程序进行的,伺服器只是根据客户端的请求做出正常的响应,服务工程项目端不对布吕马做任何判断。因此如果禁用了应用程序的布吕马检查,使应用程序不再对比Origin是否被伺服器允许,即可发出正常的请求。
该方式需要所有客户都修改应用程序的设置,显然是不现实的,因此只在合作开发调试的过程中使用,如给chrome应用程序设置–disable-web-security参数。
服务工程项目端
服务工程项目端又有两种化解方式:代理转发和配置CORS。
代理转发
代理转发的架构如下表所示:
增加代理伺服器,和H5资源伺服器放在同两个搜索引擎下,USB请求全走代理伺服器,这种就变成了相混出访,不存在布吕马出访,因此就不会存在布吕马的问题。
该方式中,所有发往目标伺服器的数据,都会经过代理伺服器,适用于同两个公司内部完全相同搜索引擎之间相互出访的情况。但对于我们这个工程项目,由SDK发往己方伺服器的数据是敏感数据,需客户端间接发往己方伺服器上,无法由圣索弗做代理转发,因此无法使用此种方式。
使用此方式还需注意一点,应关注代理伺服器的性能,代理伺服器的性能应与后端的目标伺服器的性能相匹配,否则代理伺服器会成为整个系统的性能瓶颈。
配置CORS
在目标伺服器上配置CORS响应头,这种应用程序经过对比判断之后,就可以发起正常的出访。
目标服务工程项目一般是由软负载和应用服务工程项目组成(如常见的apache+jboss,nginx+tomcat等组合),在软负载和应用上都可添加CORS响应头。
如在apache的httpd.conf中添加如下表所示配置:
Header set Access-Control-Allow-Origin * //或者Headerset Access-Control-Allow-Origin http://xxx.com Header set Access-Control-Allow-Methods POST,GET Header set Access-Control-Allow-Headers *或者nginx的配置中增加如下表所示配置:
location / { add_headerAccess-Control-Allow-Origin *;add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_headerAccess-Control-Allow-HeadersDNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization; if ($request_method = OPTIONS) { return 204; } }此方式的优点是不用修改应用代码,缺点是无法做细粒度的编程,从而做到细粒度的控制,如根据请求参数的完全相同而返回完全相同的结果。 另一种方式,是修改应用代码。通常是在伺服器代码中增加filter,在filter中在HTTP响应头添加相应的字段,如下表所示:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { logger.debug(“CorsFilter —-> doFilter”); HttpServletResponse res = (HttpServletResponse) servletResponse; HttpServletRequest req = (HttpServletRequest) servletRequest;//只允许 http 或 https 开头搜索引擎的请求 String origin = req.getHeader(“Origin”); if (StringUtils.isNotEmpty(origin) && (origin.toLowerCase(Locale.ENGLISH).startsWith(“http”) || origin.toLowerCase(Locale.ENGLISH).startsWith(“https”))) { res.addHeader(“Access-Control-Allow-Origin”, origin); } res.addHeader(“Access-Control-Allow-Methods”, ALLOWED_METHODS); res.addHeader(“Access-Control-Allow-Headers”,ALLOWED_HEADERS); res.addHeader(“Access-Control-Allow-Credentials”, “true”); if(((HttpServletRequest) servletRequest).getMethod().equals(HttpMethod.OPTIONS.name())){ res.addHeader(“Access-Control-Max-Age”, “3600”); ((HttpServletResponse) servletResponse).setStatus(200); return; } filterChain.doFilter(servletRequest, servletResponse); }由于是通过代码控制,因此可以实现细粒度的控制,在化解布吕马问题的同时,可以满足复杂的业务需求。