跨域的原理与解决方法( 二 )


跨域的原理与解决方法

文章插图
 
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资源服务器放在同一个域名下,接口请求全走代理服务器,这样就变成了同源访问,不存在跨域访问,因此就不会存在跨域的问题 。
该方式中,所有发往目标服务器的数据,都会经过代理服务器,适用于同一个公司内部不同域名之间相互访问的情况 。但对于我们这个项目,由SDK发往我方服务器的数据是敏感数据,需客户端直接发往我方服务器上,不能由合作方做代理转发,因此不能使用此种方式 。
【跨域的原理与解决方法】使用此方式还需注意一点,应关注代理服务器的性能,代理服务器的性能应与后端的目标服务器的性能相匹配,否则代理服务器会成为整个系统的性能瓶颈 。
配置CORS在目标服务器上配置CORS响应头,这样浏览器经过对比判断之后,就可以发起正常的访问 。
目标服务一般是由软负载和应用服务组成(如常见的Apache+jboss,Nginx+Tomcat等组合),在软负载和应用上都可添加CORS响应头 。
如在apache的httpd.conf中添加如下配置:
Header set Access-Control-Allow-Origin *//或者Header set Access-Control-Allow-Origin http://xxx.comHeader set Access-Control-Allow-Methods POST,GETHeader set Access-Control-Allow-Headers *或者nginx的配置中增加如下配置:
location / {add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,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);}


推荐阅读