当前位置: 首页 > news >正文

ajax跨域问题

2019独角兽企业重金招聘Python工程师标准>>> 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. * @author ding.shuai

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

  15. */

  16. public class AppUtil {

  17.  
  18. /**

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

  20. * @param request

  21. * @param result

  22. * @return

  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代码:

<body>
    <input type="button" οnclick="testJsonp();" value="TestJsonP">
</body>

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

相关文章:

  • 菜根谭#89
  • Kubernetes上的十大应用程序
  • 开发技巧:高效的使用 Response.Redirect
  • 正则表达式-基础知识Review
  • Andrew Ng机器学习公开课笔记 -- 线性回归和梯度下降
  • 四则运算1
  • Windows API 第15篇 GetVolumeInformation 获取磁盘卷(驱动器)信息
  • 看完这篇文章,你还觉得Python难吗?
  • 使用AIR进行移动APP开发常见功能和问题(上)
  • 应用el-tabs模拟nav menu组件
  • “ an error occurred during ssl communication”--VisualSVN
  • mybatis 动态SQL .1
  • 从零开始编写自己的C#框架(2)——开发前准备工作
  • 下列关于异常处理的描述中,错误的是()。
  • centos搭建svn 服务器 并同步到web 目录(总结)
  • 【Leetcode】101. 对称二叉树
  • 【RocksDB】TransactionDB源码分析
  • 0基础学习移动端适配
  • linux安装openssl、swoole等扩展的具体步骤
  • mysql 5.6 原生Online DDL解析
  • tensorflow学习笔记3——MNIST应用篇
  • 将回调地狱按在地上摩擦的Promise
  • 前端技术周刊 2019-02-11 Serverless
  • 驱动程序原理
  • 如何编写一个可升级的智能合约
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 学习HTTP相关知识笔记
  • 责任链模式的两种实现
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • (1) caustics\
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (二十四)Flask之flask-session组件
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)Windows2003安全设置/维护
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .FileZilla的使用和主动模式被动模式介绍
  • .net core开源商城系统源码,支持可视化布局小程序
  • .net 托管代码与非托管代码
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .net连接MySQL的方法
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • [Android] Implementation vs API dependency
  • [C++基础]-初识模板
  • [CodeForces-759D]Bacterial Melee
  • [GN] Vue3.2 快速上手 ---- 核心语法2
  • [Hadoop in China 2011] Hadoop之上 中国移动“大云”系统解析
  • [HDOJ4911]Inversion
  • [iOS]Win8下iTunes无法连接iPhone版本的解决方法
  • [Java][算法 双指针]Day 02---LeetCode 热题 100---04~07
  • [LeetCode] 148. Sort List 链表排序