聊聊跨域的原理与解决方法

2022-12-11 0 657

在前段时间的工程项目中,碰到这种两个情景:圣索弗合作开发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); }

由于是通过代码控制,因此可以实现细粒度的控制,在化解布吕马问题的同时,可以满足复杂的业务需求。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务