• Javascript跨域后台设置拦截


    子域名之间互相访问需要跨域

    结论放在开头:

    1. 服务端必须设置允许跨域
    2. 客户端带cookie需要设置withCredentials
    3. 无论服务端是否允许跨域,该request都会完整执行
    4. options预请求需要设置返回空,不然requestMapping没有支持该方法则出错

    环境搭建

    需求

    首先需要搭建两个环境。一个是提供API的server A,一个是需要跨域访问API的server B。

    Server A提供了一个api。完整的请求request是:

    https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759&_=1490855801818
    

    Server B有个页面page:

    http://cros.corstest.com.net:3001/test.html
    

    并且这个page需要请求server A的api。

    但由于跨域保护,请求失败:

    No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'xxxxx' is therefore not allowed access.
    

    修改host

    首先本地配置两个指向127.0.0.1的host,方便互相跨域。

    127.0.0.1   local.corstest.com.net 
    127.0.0.1   cros.corstest.com.net
    

    启动项目A,方便提供API。
    至于项目B,测试跨域只要写个html静态页面即可。那么就写一个test.html,并通过一个工具发布:

    browser-sync

    安装

    npm install -g browser-sync
    

    本地启动一个test.html

    browser-sync start --server --files "*.html" --host "cros.corstest.com.net"  --port 3001
    

    关于跨域CORS

    ruanyifeng的文章里说浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

    其中同时满足一下2种标准即为简单跨域:

    1) 请求方法是以下三种方法之一:
    HEAD
    GET
    POST
    2)HTTP的头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
    

    而其他情况,非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUTDELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),即options请求。

    关键

    跨域的关键是浏览器获得服务器的认可,而服务器的认可就是header里的Access-Control-Allow-Origin。浏览器通过比较服务端返回的response中是否包含这个字段,以及包含这个字段的内容是否是当前网址来确定是否跨域。也就是说绕过浏览器是可以不用跨域的。

    有个问题,看好多文章并没有指出。
    第一点,带cookie问题。浏览器设置withCredentialstrue则会带cookie发送给服务端。而服务端设置Access-Control-Allow-Credentialstrue则接收,false则不接受。关键是到filter里的时候才会决定是否设置response,那么这时候cookie已经存在request里了吧。(待验证)

    验证:server端确实已经接受了cookie,即使设置为false,服务端仍旧接受cookie。而客户端也仍旧可以发送cookie。

    第二点,简单跨域中,浏览器的请求直接发送给服务器,服务器返回是否支持跨域(即是否header加origin), 那么简单跨域究竟是请求了服务端几次?如果是1次,那么如果服务端不支持跨域,即没有设置allow,还会不会继续走下去,会不会继续request得到结果后放入response?就是不论跨域不跨域服务器是否都会执行这个request对应的计算。因为所有的设置header都是给浏览器告知的,和服务端限制无关。(待验证)

    验证:即使服务端没有设置允许跨域,当客户端请求过来时,服务端仍旧完整执行了请求并返回,只是客户端没有接收。

    服务端需要做点工作

    针对上述两种跨域。server A需要写一个filter。

    <filter>
        <filter-name>cors</filter-name>
            <filter-class>com.test.filter.CorsFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>cors</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    </filter>
    

    Filter:

    public class CorsFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            URL requestURL = new URL(request.getRequestURL().toString());
            String hostName = requestURL.getHost();
            String origin = request.getHeader("Origin");
    
            int index = hostName.indexOf(".");
    
            if(index > -1) {
                String domainHost = hostName.substring(index, hostName.length());
                if(!StringUtils.isEmpty(origin) && origin.contains(domainHost)) {
                    response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
                    response.addHeader("Access-Control-Allow-Origin", origin);
                    response.addHeader("Access-Control-Allow-Credentials", "true");
                    response.setHeader("Access-Control-Max-Age", "3600");
                    response.addHeader("Access-Control-Allow-Headers", "Content-Type, Cookie, " +
                            "Accept-Encoding, User-Agent, " +
                            "Host, Referer, " +
                            "X-Requested-With, Accept, " +
                            "Accept-Language, Cache-Control, Connection");
    
                    if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
                        // CORS "pre-flight" request
                        response.setStatus(200);
                        return;
                    }
                }
            }
    
            filterChain.doFilter(request, response);
        }
    }
    

    上述filter是为了同一个domain下,不同子域名可以跨域访问,而其他domain则不可以,因为我们需要共享cookie,所以设置Access-Control-Allow-Credentialstrue. 如果设置为false则不接受cookie。

    客户端,即server B如果想要发送cookie则需要设置withCredentialstrue.

    //原生
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    //jquery
    $.ajax({
        ...
        xhrFields: {
            withCredentials: true
        }
        ...
    }); 
    
    

    注意,针对非简单跨域的时候发送options请求,服务端A需要告诉浏览器是否支持跨域即可,不要往下走了,不然到指定的requestMapping发现不支持这个方法就会很尴尬了,所以直接返回。

    下面针对简单跨域和非简单跨域做测试:

    <!DOCTYPE html>
    <html lang="en">
    
        <meta charset="UTF-8">
        <title>test</title>
    
        <script src="jquery-1.11.3.js"></script>
    </head>
    <body>
    
    <input type="button" value="GET_Default" onclick="testGetDefault()">
    <input type="button" value="GET_JSON" onclick="testGetJSON()">
    <input type="button" value="POST_Default" onclick="testPostDefault()">
    <input type="button" value="POST_JSON" onclick="testPostJson()">
    <input type="button" value="PUT" onclick="testPUT()">
    
    <script>
        var getUrl = "https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759";
        var postUrl = "https://local.corstest.com.net:8443/contentmain/saveReservationDeposits.json?htid=759";
    
        function testGetDefault(){
            sendAjax("GET",getUrl, "json", "application/x-www-form-urlencoded");
        }
        function testGetJSON(){
            sendAjax("GET",getUrl, "json", "application/json; charset=utf-8");
        }
        function testPostDefault(){
            sendAjax("POST",postUrl, "json", "application/x-www-form-urlencoded");
        }
    
        function testPostJson(){
            sendAjax("POST",postUrl, "json", "application/json; charset=utf-8");
        }
    
        function testPUT(){
            sendAjax("PUT",postUrl, "json", "application/json; charset=utf-8");
        }
    
        
        function sendAjax(type, url, dataType, contentType){
            $.ajax( { 
                type: type,
                url:  url,
                xhrFields: {
                    withCredentials: true
                },
                dataType : dataType, // accept type
                contentType: contentType,  //request type, default is application/x-www-form-urlencoded
                success: function(result){
                    console.log(result);
                },
                error: function (xhr) {
                    console.log(xhr);
                }
            });
        }
    
    
    </script>
    </body>
    </html>
    

    结果:

    GET default:
    只发送一个正常的get请求。

    GET json:

    先发送一个options如下:

    General:
    Request URL:https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759
    Request Method:OPTIONS
    Status Code:200 OK
    Remote Address:127.0.0.1:8443
    
    Response Headers:
    Access-Control-Allow-Credentials:true
    Access-Control-Allow-Headers:Content-Type, Cookie, Accept-Encoding, User-Agent, Host, Referer, X-Requested-With, Accept, Accept-Language, Cache-Control, Connection
    Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
    Access-Control-Allow-Origin:http://cros.corstest.com.net:3001
    Content-Length:0
    Date:Thu, 30 Mar 2017 12:47:44 GMT
    Server:Apache-Coyote/1.1
    
    Request Headers:
    Accept:*/*
    Accept-Encoding:gzip, deflate, sdch, br
    Accept-Language:zh-CN,zh;q=0.8
    Access-Control-Request-Headers:content-type
    Access-Control-Request-Method:GET
    Connection:keep-alive
    Host:local.corstest.com.net:8443
    Origin:http://cros.corstest.com.net:3001
    Referer:http://cros.corstest.com.net:3001/test.html
    User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
    
    

    然后再发送正常的Get请求。

    post default:
    正常发送请求。

    post json: 先发送一个options请求。然后再发送正常的请求。
    其他同理,总之,非简单跨域会多发一次options请求来确认是否支持跨域,这时候服务端一定要返回支持跨域,并且直接返回即可。

  • 相关阅读:
    2013第2周四晴
    2012第53周&2013第1周日
    2013周六雪转阴
    2013年第二周日生活整理
    php技术–php中感叹号!和双感叹号!!的用法(三元运算)
    laravel拓展validator验证
    laravel 5 自定义全局函数,怎么弄呢?
    Laravel 清空配置缓存
    网上很多laravel中cookie的使用方法。
    艾伟也谈项目管理,给敏捷团队中的架构师的10个建议 狼人:
  • 原文地址:https://www.cnblogs.com/woshimrf/p/js-cors.html
Copyright © 2020-2023  润新知