目前常用的Ajax跨域问题解决方式有三种
Jsonp方式,需要前端和后端同时处理nginx(或者其它HTTP服务器)配置后端设置CORS允许跨域本文着重讲后端设置跨域,案例基于SpringBoot,其它框架原理相同
CORS是一个W3C标准,全称是"跨域资源共享",CORS有两种请求,简单请求和非简单请求。
(1) 只要同时满足以下两大条件,就属于简单请求。
请求方法是以下三种方法之一: HEADGETPOST HTTP的头信息不超出以下几种字段 AcceptAccept-LanguageContent-LanguageLase-Event-IDContent-Type: 只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain(2) 对于非简单请求,会发出一次预检测请求,返回码是204,预检测通过后,才会真正发出请求。
(3) 后端CORS处理方式
使用Filter过滤器,对所有的请求的response添加header 在SpringBoot中实现Filter接口,并且设置过滤器的顺序为最前@Order(-Integer.MAX_VALUE)
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*"); if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpStatus.NO_CONTENT.value()); } else { filterChain.doFilter(request, response); } }此处常规情况跨域可以解决,但是某些情况下,在IOS系统下该配置会失效,在发起option请求得到返回204状态码之后,不再进行下一步的调用。经过分析后,发现是
由于header里面带了很多请求参数,而上面的response.setHeader("Access-Control-Allow-Headers", "*");允许所有头部没有生效导致。 但是在PC浏览器或者安卓手机上,该方式又能生效(烦人的IOS兼容问题,找了好久才找到)。
找到问题后,开始对上面的代码进行了改造,由于项目中不同地方的接口可能header中的某些参数不一致,所以决定采用前端传了什么header,就在response中设置允许什么header,改造后的代码如下。
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); /**cors modified start**/ StringBuilder headers = new StringBuilder(); Enumeration<String> headerNames = request.getHeaders("Access-Control-Request-Headers"); if(Objects.nonNull(headerNames)) { while (headerNames.hasMoreElements()) { headers.append(headerNames.nextElement()).append(","); } } response.setHeader("Access-Control-Allow-Headers", headers.toString()); /**cors modified end**/ if (HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpStatus.NO_CONTENT.value()); } else { filterChain.doFilter(request, response); } }jsonp的跨域实现原理是在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的,src或href链接的静态资源,本质上来说也是一个get请求。
jsonp就是利用这一策略,从服务端拿到信息之后,利用回调callbak回调页面上的方法(前后端约定),进行渲染,所有jsonp只能处理get请求。
具体本文不做详细介绍,具体可查看Jsonp跨域原理解析
nginx配置跨域原理和后端实现类似,也是通过设置header 里面参数实现。
location / { if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always; add_header Access-Control-Allow-Headers 'Authorization,X-Requested-With,Content-Type,Origin,Accept' always; add_header Access-Control-Max-Age 3600; add_header Content-Length 0; return 200; } add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always; add_header Access-Control-Allow-Headers 'Authorization,X-Requested-With,Content-Type,Origin,Accept' always; proxy_pass http://localhost:8081/; }具体可以查看nginx方向代理解决跨域
(1) canvas操作图片的跨域问题。
以前做微信公众号生成海报时,就遇到过这问题。
主要原理是利用HTML的crossOrigin属性,通过设置crossOrigin=anonymous。具体参考解决canvas图片getImageData,toDataURL跨域问题