博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ajax跨域问题
阅读量:6537 次
发布时间:2019-06-24

本文共 10544 字,大约阅读时间需要 35 分钟。

hot3.png

前言

 

从刚接触前端开发起,跨域问题就出现过很多次,以前也整理过一篇,也找不到了,此处重新整理。

 

题纲

关于跨域,有N种类型,本文只专注于ajax请求跨域(,ajax跨域只是属于浏览器”同源策略”中的一部分,其它的还有Cookie跨域iframe跨域,LocalStorage跨域等这里不做介绍),内容大概如下:

  • 什么是ajax跨域

    • 原理

    • 表现(整理了一些遇到的问题以及解决方案)

  • 如何解决ajax跨域

    • JSONP方式

    • CORS方式

    • 代理请求方式

  • 如何分析ajax跨域

    • http抓包的分析

    • 一些示例

 

什么是ajax跨域

 

ajax跨域的原理

 

ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”,可以参考浏览器同源政策及其规避方法(阮一峰)

 

CORS请求原理

 

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

 

基本上目前所有的浏览器都实现了CORS标准,其实目前几乎所有的浏览器ajax请求都是基于CORS机制的,只不过可能平时前端开发人员并不关心而已(所以说其实现在CORS解决方案主要是考虑后台该如何实现的问题)。

关于CORS,强烈推荐阅读跨域资源共享 CORS 详解(阮一峰)

 

JSONP的实现步骤大致如下(参考了来源中的文章)

jsonp解决跨域问题的原理是,浏览器的script标签是不受同源策略限制的,我们可以在script标签中访问任何域名下的资源文件。利用这一特性,用script标签从服务器中请求数据,同时服务器返回一个带有方法和数据的js代码,请求完成,调用本地的js方法,来完成数据的处理。

前端实现,以Jquery的ajax方法为例:

 

 
  1. $.ajax({

  2. url:"",

  3. dataType:'jsonp',

  4. data:'',

  5. jsonp:'callback', //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)

  6.  
  7. success:function(result) {

  8. //成功的处理

  9. },

  10. error:function(){

  11. //错误处理

  12. }

  13. });

 

服务端此时返回的不能是普通的json字符串,而是一段可以被前端js执行的一段js代码。

比较一下json与jsonp格式的区别:

json格式:

 
  1. {

  2. "message":"获取成功",

  3. "state":"1",

  4. "result":{"name":"工作组1","id":1,"description":"11"}

  5. }

jsonp格式:

 
  1. callback({

  2. "message":"获取成功",

  3. "state":"1",

  4. "result":{"name":"工作组1","id":1,"description":"11"}

  5. })

从格式来看,jsonp是在json的基础上包装了一个方法名,此方法名是前端请求传过来的,如请求地址为:http://localhost:9999/tookApp/tbk/getItem?callback=JSONP_CALLBACK,那么方法名就是JSONP_CALLBACK。

--------------------- 本文来自 晓梦_知行 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/csdn_ds/article/details/73691134?utm_source=copy

面提供一段java代码,对象转jsonp的工具类:

 
  1. package com.tooklili.app.web.util;

  2.  
  3. import javax.servlet.http.HttpServletRequest;

  4.  
  5. import org.apache.commons.lang.StringUtils;

  6. import org.springframework.web.context.request.RequestContextHolder;

  7. import org.springframework.web.context.request.ServletRequestAttributes;

  8.  
  9. import com.fasterxml.jackson.databind.util.JSONPObject;

  10.  
  11. /**

  12. *

  13. * ding.shuai

  14. * 2016年8月15日上午9:47:02

  15. */

  16. public class AppUtil {

  17.  
  18. /**

  19. * 判断json字符串是否需要转化成jsonp格式

  20. * request

  21. * result

  22. *

  23. */

  24. public static Object conversionJsonp(Object result){

  25. HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

  26. return conversionJsonp(request, result);

  27. }

  28.  
  29.  
  30. public static Object conversionJsonp(HttpServletRequest request,Object result){

  31. String callback = request.getParameter("callback");

  32. if(StringUtils.isNotEmpty(callback)){

  33. return new JSONPObject(callback, result);

  34. }

  35. return result;

  36. }

  37. }

jsonp的缺点:

1、JSONP是一种非官方的方法,而且这种方法只支持GET方法,不如POST方法安全。(从实现机制就可明白)。

2、JSONP的实现需要服务器配合,如果是访问的是第三方的服务器,我们没有修改服务器的权限,那么这种方式是不可行的

基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)

CORS解决跨域问题

后端应该如何配置以解决问题(因为大量项目实践都是由后端进行解决的),这里整理了一些常见的后端解决方案:

JAVA后台配置

JAVA后台配置只需要遵循如下步骤即可:

 

  • 第一步:获取依赖jar包下载 cors-filter-1.7.jar, java-property-utils-1.9.jar 这两个库文件放到lib目录下。(放到对应项目的webcontent/WEB-INF/lib/下)

  • 第二步:如果项目用了Maven构建的,请添加如下依赖到pom.xml中:(非maven请忽视)

 

 

<dependency>

    <groupId>com.thetransactioncompany</groupId>

    <artifactId>cors-filter</artifactId>

    <version>[ version ]</version>

</dependency>

 

其中版本应该是最新的稳定版本,CORS过滤器

 

  • 第三步:添加CORS配置到项目的Web.xml中(  App/WEB-INF/web.xml)

 

 

<!-- 跨域配置-->    

<filter>

        <!-- The CORS filter with parameters -->

        <filter-name>CORS</filter-name>

        <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>

        

        <!-- Note: All parameters are options, if omitted the CORS

             Filter will fall back to the respective default values.

          -->

        <init-param>

            <param-name>cors.allowGenericHttpRequests</param-name>

            <param-value>true</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.allowOrigin</param-name>

            <param-value>*</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.allowSubdomains</param-name>

            <param-value>false</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.supportedMethods</param-name>

            <param-value>GET, HEAD, POST, OPTIONS</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.supportedHeaders</param-name>

            <param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.exposedHeaders</param-name>

            <!--这里可以添加一些自己的暴露Headers   -->

            <param-value>X-Test-1, X-Test-2</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.supportsCredentials</param-name>

            <param-value>true</param-value>

        </init-param>

        

        <init-param>

            <param-name>cors.maxAge</param-name>

            <param-value>3600</param-value>

        </init-param>

 

    </filter>

 

    <filter-mapping>

        <!-- CORS Filter mapping -->

        <filter-name>CORS</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

 

请注意,以上配置文件请放到web.xml的前面,作为第一个filter存在(可以有多个filter的)

 

  • 第四步:可能的安全模块配置错误(注意,某些框架中-譬如公司私人框架,有安全模块的,有时候这些安全模块配置会影响跨域配置,这时候可以先尝试关闭它们)

 

NET后台配置

 

.NET后台配置可以参考如下步骤:

 

  • 第一步:网站配置

 

打开控制面板,选择管理工具,选择iis;右键单击自己的网站,选择浏览;打开网站所在目录,用记事本打开web.config文件添加下述配置信息,重启网站

 

d6315c64557ecc3820a2ae68f90062120bf.jpg

 

请注意,以上截图较老,如果配置仍然出问题,可以考虑增加更多的headers允许,比如:

 

"Access-Control-Allow-Headers":"X-Requested-With,Content-Type,Accept,Origin"

 

  • 第二步:其它更多配置,如果第一步进行了后,仍然有跨域问题,可能是:

    • 接口中有限制死一些请求类型(比如写死了POST等),这时候请去除限 制

    • 接口中,重复配置了Origin:*,请去除即可

    • IIS服务器中,重复配置了Origin:*,请去除即可

 

代理请求方式解决接口跨域问题

 

注意,由于接口代理是有代价的,所以这个仅是开发过程中进行的。

 

与前面的方法不同,前面CORS是后端解决,而这个主要是前端对接口进行代理,也就是:

 

  • 前端ajax请求的是本地接口

  • 本地接口接收到请求后向实际的接口请求数据,然后再将信息返回给前端

  • 一般用node.js即可代理

 

关于如何实现代理,这里就不重点描述了,方法和多,也不难,基本都是基于node.js的。

 

搜索关键字node.js,代理请求即可找到一大票的方案。

 

如何分析ajax跨域

 

上述已经介绍了跨域的原理以及如何解决,但实际过程中,发现仍然有很多人对照着类似的文档无法解决跨域问题,主要体现在,前端人员不知道什么时候是跨域问题造成的,什么时候不是,因此这里稍微介绍下如何分析一个请求是否跨域:

 

抓包请求数据

 

第一步当然是得知道我们的ajax请求发送了什么数据,接收了什么,做到这一步并不难,也不需要fiddler等工具,仅基于Chrome即可

 

  • Chrome浏览器打开对应发生ajax的页面,F12打开Dev Tools

  • 发送ajax请求

  • 右侧面板->NetWork->XHR,然后找到刚才的ajax请求,点进去

 

示例一(正常的ajax请求)

 

7ebbb87a08509e3555a5f89f267d7d12c88.jpg

 

上述请求是一个正确的请求,为了方便,我把每一个头域的意思都表明了,我们可以清晰的看到,接口返回的响应头域中,包括了

 

Access-Control-Allow-Headers: X-Requested-With,Content-Type,Accept

Access-Control-Allow-Methods: Get,Post,Put,OPTIONS

Access-Control-Allow-Origin: *

 

所以浏览器接收到响应时,判断的是正确的请求,自然不会报错,成功的拿到了响应数据。

 

示例二(跨域错误的ajax请求)

 

为了方便,我们仍然拿上面的错误表现示例举例。

1360f68163a4533b138c4cb92c63062deef.jpg

 

这个请求中,接口Allow里面没有包括OPTIONS,所以请求出现了跨域、

 

4291a6a763788e2ce6132e8c8db34a2372d.jpg

 

这个请求中,Access-Control-Allow-Origin: *出现了两次,导致了跨域配置没有正确配置,出现了错误。

 

更多跨域错误基本都是类似的,就是以上三样没有满足(Headers,Allow,Origin),这里不再一一赘述。

 

示例三(与跨域无关的ajax请求)

 

当然,也并不是所有的ajax请求错误都与跨域有关,所以请不要混淆,比如以下:

 

01ef4eca1dcd14e475ba26d28fb5a501018.jpg

 

 cf3dd65ccf64c073b28704e9a6ceefac168.jpg

 

比如这个请求,它的跨域配置没有一点问题,它出错仅仅是因为request的Accept和response的Content-Type不匹配而已。

另:

方法1. jsonp实现ajax跨域访问示例

jsp代码:

    

js代码:

复制代码

function testJsonp(){    $.ajax({        type : 'GET',        dataType : 'jsonp', // 数据类型配置成jsonp        jsonp : "callback", //配置jsonp随机码标签,在服务器代码部分需要用到他来拼接一个json的js对象        url : 'http://127.0.0.1:8001/test', //服务路径        async : false,        data: {            "type":'0',        },        success : function (response) {            if(response.code == 200){                alert('返回成功!');            }else{                alert('服务器异常!');            }        },        error : function (){            alert('服务器异常!');        }    });}

复制代码

java代码:

复制代码

@RequestMapping(value = "/test", method = RequestMethod.GET)    public @ResponseBody String testJsonp(@RequestParam(value = "type", defaultValue = "") String type, String callback) {        RequestResult data = new RequestResult(); // 配置需要返回的结果        data.setCode(200);        data.setMessage("success");        // 接收参数callback名称需要与js中配置的jsonp标签名一致        String result = callback+"("+JSONObject.fromObject(data).toString()+")";//拼接可执行的js        return result;    }

复制代码

 

几个注意点:

1. jsonp只支持GET方式的请求,无论ajax中的type配置成何种方式,都会在默认以GET方式发送请求。无法满足restful方式的请求。

2. ajax中的jsonp的标签名要与服务端的接收标签参数一致。本例中都设置为'callback'.

3. 若想传递一个json格式的js对象到服务器,可以使用JSON.stringify()方法将js对象转化为json字符串,赋值到某个变量。在服务器端获取这个变量的值通过json工具,将该字符串转化为java对象。

 

方法2. 在服务器端放开访问权限,使允许接收ajax访问

前端代码不需修改。

java代码, 在BaseController中加入:   

复制代码

    // protected HttpServletRequest request;    protected HttpServletResponse response;    // protected HttpSession session;    @ModelAttribute    public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) {        // this.request = request;        this.response = response;        // this.session = request.getSession();        response.setHeader("Access-Control-Allow-Origin", "*");    }    @RequestMapping(value = "/test", method = RequestMethod.GET)    public @ResponseBody RequestResult test() {        RequestResult result = new RequestResult();        result.setCode(200);        result.setMessage("success");        return result;    }

复制代码

说明:ModelAttribute的作用

1)放置在方法的形参上:表示引用Model中的数据
2)放置在方法上面:表示请求该类的每个Action前都会首先执行它,也可以将一些准备数据的操作放置在该方法里面。

这种方式只允许GET方式的请求,无法满足restful格式的请求。

 

方法3.终极解决办法(方法2的升级版)

前端代码不需修改。

在服务器端写一个filter,在doFilter方法中全面放开访问权限,并将此filter配置到web.xml中,或直接使用注解,划入spring管理。此方法完全支持restful的ajax跨域请求。

下面给一下spring mvc框架下的一个解决示例(亲测可用):

复制代码

import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletResponse;import org.springframework.stereotype.Component;@Component("myFilter")public class MyFilter implements Filter {    public void destroy() {        // System.out.println("过滤器销毁");    }    public void doFilter(ServletRequest request, ServletResponse response1, FilterChain chain) throws IOException,            ServletException {        // System.out.println("执行过滤操作");        HttpServletResponse response = (HttpServletResponse) response1;        response.setHeader("Access-Control-Allow-Origin", "*");        response.setHeader("Access-Control-Allow-Headers",                "User-Agent,Origin,Cache-Control,Content-type,Date,Server,withCredentials,AccessToken");        response.setHeader("Access-Control-Allow-Credentials", "true");        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");        response.setHeader("Access-Control-Max-Age", "1209600");        response.setHeader("Access-Control-Expose-Headers", "accesstoken");        response.setHeader("Access-Control-Request-Headers", "accesstoken");        response.setHeader("Expires", "-1");        response.setHeader("Cache-Control", "no-cache");        response.setHeader("pragma", "no-cache");        chain.doFilter(request, response);    }    public void init(FilterConfig arg0) throws ServletException {        // System.out.println("过滤器初始化");    }}

 

更多

基本上都是这样去分析一个ajax请求,通过Chrome就可以知道了发送了什么数据,收到了什么数据,然后再一一比对就知道问题何在了。

 

 

写在最后的话

跨域是一个老生常谈的话题,网上也有大量跨域的资料,并且有不少精品(比如阮一峰前辈的),

漫漫前端路,望与诸君共勉之!

参考资料

  • 浏览器同源政策及其规避方法(阮一峰)

  • 跨域资源共享 CORS 详解(阮一峰)

  • Ajax跨域(jsonp) 调用JAVA后台 (http://www.cnblogs.com/holdon521/p/5282354.html)

    springMVC获取request和response (http://blog.sina.com.cn/s/blog_7085382f0102v9jg.html)

    REST跨域访问解决CorsFilter (http://blog.csdn.net/u013628152/article/details/49490213)

    Spring Boot 过滤器、监听器 (http://blog.csdn.net/catoop/article/details/50501688)

越努力越幸运

转载于:https://my.oschina.net/u/2401092/blog/2223157

你可能感兴趣的文章
“灾备全生态”全揭秘
查看>>
Zeppelin Prefix not found.
查看>>
linux 的网络设置
查看>>
首届“欧亚杯”象翻棋全国团体邀请赛圆满收评!
查看>>
编译tomcat
查看>>
oracle-xe手工创建数据库
查看>>
我的友情链接
查看>>
UG中卸载被占用的DLL
查看>>
eclipse 设置注释模板详解,与导入模板方法介绍总结
查看>>
Cocos2d-x3.2 文字显示
查看>>
估计下星期就能考科目二了
查看>>
轻松实现localStorage本地存储和本地数组存储
查看>>
mongodb group
查看>>
python+selenium自动化测试(二)
查看>>
(笔记 - 纯手敲)Spring的IOC和AOP 含GIT地址
查看>>
7-设计模式介绍
查看>>
JDK 1.7+Android SDK+IntelliJ IDEA 13+Genymotion 安卓开发环境部署
查看>>
session_start()放置位置的不正确引发的ROOT常量 未定义的错误
查看>>
如何设定VDP同时备份的任务数?
查看>>
ipsec的***在企业网中的经典应用
查看>>