美文网首页
跨域问题

跨域问题

作者: EmmaQin | 来源:发表于2018-11-01 16:52 被阅读0次

1. 是什么?

  • 同域:相同域名,端口相同,协议相同。

  • 跨域:浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了。

  • 同源策略:请求的url地址必须与浏览器上的url地址处于同域上。
    比如:我在本地上的域名是study.cn,请求另外一个域名一段数据。就会报错404。
    为什么有同源策略保护:如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险。

如果域名是study.cn/json/jsonp/jsonp.html,那么有以下几种情况

请求地址 形式 结果
http://study.cn/test/a.html 同一域名,不同文件夹 成功
http://study.cn/json/jsonp/jsonp.html 同一域名,统一文件夹 成功
http://a.study.cn/json/jsonp/jsonp.html 不同域名,文件路径相同 失败
http://study.cn:8080/json/jsonp/jsonp.html 同一域名,不同端口 失败
https://study.cn/json/jsonp/jsonp.html 同一域名,不同协议 失败

2. 怎么解决跨域问题

1. jsonp

  • jsonp:全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
    一个是描述信息的格式,一个是信息传递双方约定的方法。

  • jsonp的产生:

    1. AJAX直接请求普通文件存在跨域无权限访问的问题
    2. 我们在调用js文件的时候又不受跨域影响,比如引入jquery框架或者是调用图片的时候
    3. 凡是拥有scr这个属性的标签都可以跨域例如<script><img><iframe>
    4. 如果想通过纯web端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进js格式的文件里
    5. 而 json又是一个轻量级的数据格式,还被js原生支持
    6. 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback 参数给服务端
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>

    <script type="text/javascript">
        var message = function(data) {
            alert(data[0].title);
        };
    </script>

    <script type="text/javascript" src="http://localhost:8080/test_war%20exploded/message.js"></script>
</head>
<body>
<div id='testdiv'></div>
</body>
</html>

另一个项目的js文件

message([
     {"id":"1", "title":"天津新闻联播,雷人搞笑的男主持人"},
     {"id":"2", "title":"楼市告别富得流油 专家:房价下跌是大概率事件"},
     {"id":"3", "title":"法国人关注时事 八成年轻人每天阅读新闻"},
     {"id":"4", "title":"新闻中的历史,历史中的新闻"},
     {"id":"5", "title":"东阳新闻20140222"},
     {"id":"6", "title":"23个职能部门要增加新闻发布频次"},
     {"id":"7", "title":"《贵州新闻联播》 中国美丽乡村"},
     {"id":"8", "title":"朝韩离散家属团聚首轮活动结束"},
     {"id":"9", "title":"索契冬奥会一天曝出两例兴奋剂事件"},
     {"id":"10", "title":"今天中国多地仍将出现中度霾"}
 ]);

这样就实现跨域成功了,因为服务端返回数据时会将这个callback参数(message)作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
注意:跨域请求是只能是get请求不能使用post请求
例子2:jsonp形式的Ajax请求

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="./js/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    var name = 'chenshishuo';
    var sex = 'man';
    var address = 'shenzhen';
    var looks = 'handsome ';
     $.ajax({
         type : 'get',
         url:'http://192.168.31.137/train/test/testjsonp',
        data : {
            name : name,
            sex : sex,
            address : address,
            looks : looks,
        },
        cache :false,
        jsonp: "callback",
        jsonpCallback:"success",
        dataType : 'jsonp',
        success:function(data){
            alert(data);
        },
        error:function(data){
            alert('error');
        }        
    });
});
</script>
</head>
<body>
<input id='inputtest' value='546' name='inputtest'>
<div id='testdiv'></div>
</body>
</html>

jsonp 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
jsonpCallback 自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
例子3:通过iframe来跨子域
基于iframe实现的跨域要求两个域具有aa.xx.com,bb.xx.com 这种特点,
也就是两个页面必须属于一个基础域(例如都是xxx.com),使用同一协议和同一端口,这样在两个页面中同时添加document.domain,就可以实现父页面调用子页面的函数。
aa.xx.com下的a.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
        <script type="text/javascript">
            document.domain = 'study.cn';
            function test() {
                alert(document.getElementById('a').contentWindow);
            }
        </script>
</head>
<body>
    <iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>

bb.xx.com下的b.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script type="text/javascript">
document.domain = 'study.cn';
</script>
</head>
<body>
    我是b.study.cn的body
</body>
</html>

原理就是让这个 iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发 送ajax请求,然后收到的数据我们也可以获得了。

2 空iframe加form

jsonp只可以发送get请求,因为本身javascript加载资源就是用get请求,所以要发post请求怎么办?

const requestPost = ({url, data}) => {
  // 首先创建一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })

  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素需要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表单提交后,就可以删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
})

3 CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求,简单请求和非简单请求。

  • 简单请求
    1. 请求方式限制:GET POST HEAD
    2. HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form- urlencoded、multipart/form-data、text/plain
    1. 原理
      发送请求,浏览器会在在header信息里面多加一个字段:
      key:origin
      value:协议+域名+端口(如https://localhost:8080)
      服务器根据对象里的origin值来决定是否同意这次请求
      如果请求通过
      请求通过后,服务器会在header里多增加几个字段:

Access-Control-Allow-Origin: https://localhost:8080
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Token

  • 解释:
    1.Credentials是否允许包含cookie,如果返回了true,那么浏览器发送请求体的时候要添加:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

如此这般,才能向服务器发送cookie
2.Access-Control-Expose-Headers是多返回的字段名,根据实际需求改变
如果请求失败
说明之前发送Origin的value不在许可范围内
服务器会返回一个正常的HTTP回应
浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段
从而抛出一个无法跨域的异常(可能是200,所以无法识别

  • 非简单请求
    非简单请求的方法就是Put,Delete,或者Content-Type是application/json
    非简单请求在发出CORS请求之前,会增加一次HTTP查询请求,也叫预检请求,预检请求成功了,浏览器才能发出XMLHttpRequest,否则报错
    如果浏览器发现这是一个非简单请求
    就会先发送一个预检请求:

OPTIONS /cors HTTP/1.1
Origin: http://localhost:8088
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: token

预测请求的方法名叫OPTIONS
Origin和简单请求一样
Access-Control-Request-Method是请求方法
Access-Control-Request-Headers是额外发送的头信息字段
(tip:预检请求一般缓存下来,以便不需要在每次请求都发送)
如果预检请求通过
我们来看下服务器发出的回应

HTTP/1.1 200 OK
Date: Nov, 01 Dec 2017 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:8088
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: token
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0

Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

如果预检请求失败
服务器会返回一个HTTP回应(没有CORS的头信息字段)
并在console打出not allowed by Access-Control-Allow-Origin异常
预检请求通过以后
浏览器发每次出的CORS请求就和简单请求一样
会有一个origin字段
服务器每次回应都会有Access-Control-Allow-Origin头信息字段

代码实现:
前端ajax请求:

$.ajax({
    type: "POST",
    url: baseUrl + "/post",
    dataType: 'json',
    crossDomain: true,
    xhrFields: {
        withCredentials: true
    },
    data: {
        name: "name_from_frontend"
    },
    success: function (response) {
        console.log(response)// 返回的 json 数据
        $("#response").val(JSON.stringify(response));
    }
});

crossDomain: true是指开启跨域

后端mvc配置:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**/*").allowedOrigins("*");
    }
}

4 Nginx代理

请求的时候还是用前端的域名,然后Nginx帮我们把这个请求转发到真正的后端域名上,避免跨域.
但如果后端接口是一个公共的API,比如一些公共服务获取天气什么的,前端调用的时候总不能让运维去配置一下Nginx,如果兼容性没问题(IE 10或者以上),CROS才是更通用的做法。


image.png

相关文章

网友评论

      本文标题:跨域问题

      本文链接:https://www.haomeiwen.com/subject/arhyzftx.html