背景

在项目(域名为a.cn)中,做单点登录时,请求b.cn域名进行用户名密码登录。项目中本域下(a.cn)以jsonp方式跨域请求时,jsonp只支持get请求,用户密码等敏感信息会被明文传输到ngnix日志上,存在泄漏的安全风险,因此采用非jsonp的方式进行跨站访问。

在b.cn服务中添加response header:

("Access-Control-Allow-Origin": "https://www.a.cn")

知识点

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
对CORS协议不了解的同学,可以点击这里

服务端引用Access-Control-Allow-Origin后带来了新的问题,b.cn域下的cookie无法传输到a.cn的域下。

下面我们就一起讨论下其中的cookie传输问题。

场景

http://a.cn/test.html向
http://b.cn/test/cors
发起ajax请求。

b.cn/test/cors种cookie(name:xudj)
test.html第二次发起请求时,希望将cookie(name:xudj)带给b.cn/test/cors。

前提

为了模拟简单一点,因为不同端口也是属于跨域请求,所以:
使用http://localhost:8182/test.html 代替http://a.cn/test.html 下面称其(客户端)
使用http://localhost:8080/load/data/cors 代替http://b.cn/test/cors 下面称其(服务端)

问题复现

localhost:8182客户端代码如下:
http://localhost:8182/test.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>项目网站</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>

    <body>
        <script src="http://code.jquery.com/jquery.min.js"></script>
        <script>
            var url = "http://localhost:8080/load/data/cors";
            $.ajax({
                url:url,
                type:"GET",
                success:function(res){ 
                    console.log(res);
                }
            }) 
        </script>
    </body> 

</html>

localhost:8080服务端代码如下
http://localhost:8080/load/data/cors

 @RequestMapping(value = "/load/data/cors", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void loadData3(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 指定允许域名访问
        response.addHeader("Access-Control-Allow-Origin", "http://localhost:8182");
        // 是否允许跨域请求携带认证信息(cookies等)
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addHeader("Access-Control-Allow-Headers", "Origin, x-requested-with, Content-Type, Accept,X-Cookie");
        response.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");

        Cookie[] cookies = request.getCookies();
        String name = "";
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("name".equals(cookie.getName())) {
                    name = cookie.getValue();
                    break;
                }
            }
        }
        if (name == null || name.equals("")) {
            // 设置cookies
            Cookie cookie = new Cookie("name", "xudj");
            cookie.setPath("/");
	    // 注意,如果response.addHeader("Access-Control-Allow-Origin", "*");配置为*是无法携带cookie的。
            response.addCookie(cookie);
        }

        System.out.println("name:" + name);

        response.getWriter().write("{\"success\", \"true\"}");
    }

image.png

且多次刷新,服务端也获取不到对应cookie。

解决

localhost:8182客户端代码如下:
http://localhost:8182/test.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>项目网站</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>

    <body>
        <script src="http://code.jquery.com/jquery.min.js"></script>
        <script>
            var url = "http://localhost:8080/load/data/cors";
            $.ajax({
                url:url,
                type:"GET",
                xhrFields:{
                    withCredentials:true
                },
                success:function(res){ 
                    console.log(res);
                }
            }) 
        </script>
    </body> 

</html>

withCredentials:true 是关键。只有加上此选项,浏览器才会跨域携带cookie。否则,即使服务器同意发送Cookie,浏览器请求也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

localhost:8080服务端代码如下(不变)
http://localhost:8080/load/data/cors

 @RequestMapping(value = "/load/data/cors", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void loadData3(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 指定允许域名访问
        response.addHeader("Access-Control-Allow-Origin", "http://localhost:8182");
        // 是否允许跨域请求携带认证信息(cookies等)
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addHeader("Access-Control-Allow-Headers", "Origin, x-requested-with, Content-Type, Accept,X-Cookie");
        response.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");

        Cookie[] cookies = request.getCookies();
        String name = "";
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("name".equals(cookie.getName())) {
                    name = cookie.getValue();
                    break;
                }
            }
        }
        if (name == null || name.equals("")) {
            // 设置cookies
            Cookie cookie = new Cookie("name", "xudj");
            cookie.setPath("/");
	    // 注意,如果response.addHeader("Access-Control-Allow-Origin", "*");配置为*是无法携带cookie的。
            response.addCookie(cookie);
        }

        System.out.println("name:" + name);

        response.getWriter().write("{\"success\", \"true\"}");
    }

说明:

  • Access-Control-Allow-Credentials: true
    与浏览器侧的withCredentials:true成对使用,表明许可服务端发cookie,且客户端会写入cookie。

  • Access-Control-Allow-Origin: http://localhost:8182
    表示服务端接收客户端的请求。如果请求时不需要带cookie,此字段可以写*,表明该站接收所有来源的ajax请求。如果需要传输cookie, 该字段只能写一个固定来源。

访问test.html,第二次时如愿在localhost:8080/load/data/cors
服务端接口下看到控制台输入:name:xudj
如图:
image.png

这说明:

1、localhost:8080服务端成功将cookie携带并写到了localhost:8182客户端域所在的网站。
2、localhost:8080服务端成功从localhost:8182客户端携带了cookie到其服务器。
3、跨域携带cookie服务端的Access-Control-Allow-Origin必须指定确定域名。报错如下:
image.png