• 跨域访问-预请求及跨域常见问题


    预请求

    参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#预请求

    简而言之,在跨域并且尝试添加一些特殊头及自定义头的情况下,由于浏览器的安全机制,会加多一次OPTIONS预请求(询问请求),与跨域服务器协商可以设置的头部信息,可以允许的HTTP协议等等信息。

    以如下图一次跨域请求为例。

    图中代码如下

     1 var settings = {
     2     type: "POST",
     3     url: 'http://www.movesun.com/cors.php?allow_method=PUT',
     4     contentType: "application/json",
     5     dataType:"json",
     6     data : {
     7         "name" : "lvyahui"
     8     },
     9     xhrFields : {
    10         // withCredentials : true
    11     },
    12     success: function(resp) {
    13         console.log(resp);
    14     }
    15     ,
    16     headers: {
    17         appkey:"87a8ea08-dbaa-11e6-b3f9-7056818a4db5",
    18         "X_forwarded-for":"10.104.239.XXX"
    19     }
    20 };
    21 $.ajax(settings);

    可以看到,这段代码在movesun.com网站下,尝试向www.movesun.com发送跨域POST 请求,并且有自定义头(Content-Type设置了application/json类型也是原因之一),因此浏览器在发送真实post请求之前,发起了一个OPTIONS请求询问。

    请求之所以可以成功,是因为后端服务器正常处理了OPTIONS请求,并且响应了正确的跨域响应头,后端代码cors.php如下

     1 <?php
     2 
     3 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
     4     header('Access-Control-Allow-Origin:*');
     5     header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type');
     6     header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS'));
     7     //header('Access-Control-Allow-Credentials:true');
     8     exit(0);
     9 }
    10 
    11 header('Access-Control-Allow-Origin:*');
    12 
    13 echo json_encode(array(
    14     'code'  =>  0,
    15     'msg'   =>  '',
    16     'data'  =>  array()
    17 ));exit(0);

    可以看到服务器判断请求类型为OPTIONS时,指定了如下几个Http响应头

    1、Access-Control-Allow-Origin  : 跨域服务器允许的来源地址(跟请求的Origin进行匹配),可以是*或者某个确切的地址,不允许多个地址。当然后台代码可以动态判断来源地址进行动态设置,这主要是因为有时需要允许任意来源访问,并且要携带Cookie,此时需要明确指定地址(原因在文后常见问题中说明),下面这段PHP代码和Java代码(注意Java代码中Cookie没有取端口,因为Cookie端口不同也算同域,可以访问到)就是取来源地址并响应

     1 if (isset($_SERVER['HTTP_REFERER'])) {
     2     $urls = parse_url($_SERVER['HTTP_REFERER']);
     3     $url = $urls['scheme'] . '://' . $urls['host'];
     4     if (isset($urls['port'])) {
     5         $url .= ':' . $urls['port'];
     6     }
     7 } else {
     8     $url = '*';
     9 }
    10 
    11 header("Access-Control-Allow-Origin: " . $url);
     1 public void filter(ContainerRequestContext requestContext) throws IOException {
     2     String origin = requestContext.getHeaderString("Origin");
     3     if(origin != null && !origin.trim().equals("")
     4             // postMan 请求的protocol 是 chrome-extension://
     5             && origin.startsWith("http://")){
     6         URL url  = new URL(origin);
     7         String strUrl = url.getProtocol() + "://" + url.getHost();
     8         if(url.getPort() > 0){
     9             strUrl += ":" + url.getPort();
    10         }
    11         originUrl = strUrl;
    12         if(!cookieDomainAuto
    13                 && (sysConfig.getCookieDomain() == null || sysConfig.getCookieDomain().equals(""))){
    14             cookieDomainAuto = true;
    15         }
    16         if(cookieDomainAuto){
    17             // 动态判断 cookie domain
    18             if(url.getHost().matches(PATTERN_IP)){
    19                 // IP
    20                 sysConfig.setCookieDomain(url.getHost());
    21             } else {
    22                 int start = url.getHost().lastIndexOf('.',url.getHost().lastIndexOf('.') - 1);
    23                 String domain;
    24                 if(start > 0){
    25                     domain = url.getHost().substring(start + 1);
    26                 }else{
    27                     domain = url.getHost();
    28                 }
    29                 // domain
    30                 sysConfig.setCookieDomain(domain);
    31             }
    32         }
    33     }
    34 }
    Java动态设置Allow-Origin与Cookie Domain

    2、Access-Control-Allow-Methods:跨域服务器允许的请求方法。经测试发现,不论Access-Control-Allow-Methods设置为简单请求还是复杂请求类型,所有的简单的请求(GET,HEAD,POST)也是可以正常请求的。

    3、Access-Control-Allow-Headers:跨域服务器允许客户端添加或自定义哪些http 头。

    下面是这两次请求的报文

    OPTIONS请求报文

     1 OPTIONS http://www.movesun.com/cors.php HTTP/1.1
     2 Host: www.movesun.com
     3 Proxy-Connection: keep-alive
     4 Pragma: no-cache
     5 Cache-Control: no-cache
     6 Access-Control-Request-Method: POST
     7 Origin: http://movesun.com
     8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
     9 Access-Control-Request-Headers: appkey, content-type, x_forwarded-for
    10 Accept: */*
    11 Referer: http://movesun.com/
    12 Accept-Encoding: gzip, deflate, sdch
    13 Accept-Language: zh-CN,zh;q=0.8
    14 
    15 
    16 HTTP/1.1 200 OK
    17 Date: Fri, 10 Mar 2017 05:48:07 GMT
    18 Server: Apache
    19 Access-Control-Allow-Origin: *
    20 Access-Control-Allow-Headers: appkey,X_forwarded-for,Content-Type
    21 Access-Control-Allow-Methods: POST
    22 Vary: User-Agent,Accept-Encoding
    23 Content-Encoding: gzip
    24 Content-Length: 20
    25 Content-Type: text/html
    26 X-Cache: MISS from SK-SQUIDDEV-11
    27 X-Cache-Lookup: MISS from SK-SQUIDDEV-11:8080

    POST请求报文

     1 POST http://www.movesun.com/cors.php HTTP/1.1
     2 Host: www.movesun.com
     3 Proxy-Connection: keep-alive
     4 Content-Length: 12
     5 Pragma: no-cache
     6 Cache-Control: no-cache
     7 Origin: http://movesun.com
     8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
     9 Content-Type: application/json
    10 Accept: application/json, text/javascript, */*; q=0.01
    11 X_forwarded-for: 10.104.239.194
    12 appkey: 87a8ea08-dbaa-11e6-b3f9-7056818a4db5
    13 Referer: http://movesun.com/
    14 Accept-Encoding: gzip, deflate
    15 Accept-Language: zh-CN,zh;q=0.8
    16 name=lvyahui

    从报文中可以看出,OPTIONS请求后台可以拿到URL中的GET参数,也就是说,如果真实请求是GET请求,则后端在处理来询问的OPTIONS请求时,就可以获取到所有查询参数了。如mozilla官网所写,笔者调试发现,一些跨域请求,即便抛出了错误的情况下,请求也真的到了后台服务器,只是响应被浏览器拦截了。

    另外,有时不想在后台代码中处理OPTIONS请求,则可以在nginx server节点下做如下配置,表示拦截处理所有OPTIONS请求。

     1 location ^~ / {
     2     if ($request_method = OPTIONS ) {
     3         add_header Content-Length 0;
     4         add_header Content-Type text/plain;
     5         add_header 'Access-Control-Allow-Origin' '*';
     6         add_header 'Access-Control-Allow-Methods' '*';
     7         add_header 'Access-Control-Allow-Headers' 'appkey,X_forwarded-for,Content-Type';
     8         return 200;
     9     }
    10 }

    常见跨域问题

    下面是一些跨域下常见的一些问题

    • 添加了跨域服务器不允许的自定义头会抛出 XMLHttpRequest cannot load http://www.movesun.com/cors.php. Request header field custom_heaer is not allowed by Access-Control-Allow-Headers in preflight response.
    • 当未设置允许某种复杂请求时,使用复杂请求就会抛出如下错误,表示真实请求使用了服务器不允许的方法。在只允许POST的情况下,GET请求是可以被发送的,HEAD也可以成功,仅仅允许GET的情况下,POST也是可以发送成功的,HEAD也可以成功 。简单请求都可以成功,等等,其实经测试发现,不论Access-Control-Allow-Methods设置为简单请求还是复杂请求类型,所有的简单的请求(GET,HEAD,POST)也是可以正常请求的。XMLHttpRequest cannot load http://www.movesun.com/cors.php. Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
    • 预处理请求没有没正常处理,这种是询问请求响应了非200状态码,会抛出 XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Response for preflight has invalid HTTP status code 405 。如  
    • 1 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
      2     header("HTTP/1.1 405 Method Not Allowed");
      3     exit(-1);
      4 }
    • 错误是来源地址不是服务器所允许的来源地址。如下,此时服务器响应 Access-Control-Allow-Origin:http://www.movesun.com,表示跨域服务器允许在Origin:http://www.movesun.com 的机器上访问,而用户试图在http://movesun.com跨域请求目的服务器http://movesun.qq.com/cors.php?allow_method=PUT:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://www.movesun.com' that is not equal to the supplied origin. Origin 'http://movesun.com' is therefore not allowed access.
    • 前端设置了携带签名标志,但是跨域服务器不允许携带,没有设置 Access-Control-Allow-Credentials:true 。如:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://movesun.com' is therefore not allowed access.
    • 前端尝试在真实请求中携带签名Cookie,跨域服务器允许携带Cookie,但是服务器允许所有来源地址,会报这个错误,在跨域携带cookie时,必须明确指定来源地址,比如 Access-Control-Allow-Origin:http://movesun.com。例如:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'http://movesun.com' is therefore not allowed access. The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute.
    并且 跨域携带Cookie时,跨域服务器处理询问请求(OPTIONS)和真实请求,都必须响应明确的来源地址和允许携带cookie的标志。否则会报上面两种错误。当然很显然的两次响应的Allow-Origin都是一致的。如下
     1 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
     2     header('Access-Control-Allow-Origin:http://movesun.com');
     3     header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type');
     4     header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS'));
     5     header('Access-Control-Allow-Credentials:true');
     6     exit(0);
     7 }
     8 header('Access-Control-Allow-Origin:http://movesun.com');
     9 header('Access-Control-Allow-Credentials:true');
    10 echo json_encode(array(
    11     'code'  =>  0,
    12     'msg'   =>  '',
    13     'data'  =>  array()
    14 ));exit(0);

    注意:文中的测试接口 在 http://movesun.com/cors.php  或者 http://www.movesun.com/cors.php,感兴趣的读者可以用这个接口测试。

  • 相关阅读:
    datatables插件适用示例
    RabbitMQ三----'任务分发 '
    ftp上传下载
    运用JS导出ecxel表格、实现文件重命名
    浅谈MySQL索引背后的数据结构及算法【转】
    SQL语句导致性能问题
    由浅入深理解索引的实现【转】
    MySQL ACID及四种隔离级别的解释
    MyISAM引擎和InnoDB引擎的特点
    MySQL复制中slave延迟监控
  • 原文地址:https://www.cnblogs.com/lvyahui/p/6530990.html
Copyright © 2020-2023  润新知