代码模拟跨域Cookie共享
前端页面:发送跨域请求,为了方便模拟这里发送跨域的简单请求即可(还不知道什么叫简单请求?戳这里)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Cookie交互机制(跨域)</title> <!--导入Jquery--> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> </head> <body> <button id="btn">Cookie交互机制(跨域)</button> <div id="content"></div> <script> $("#btn").click(function () { $.get("http://localhost:8080/corscookie"); }); </script> </body> </html>
前端页面托管在本地的63342端口上:http://localhost:63342/...
后端代码:后端接口托管在8080端口上:http://localhost:8080/...
这就是最简单的一个跨域场景,两个域具有相同的domain,因此才有共享Cookie的可能。
/** * 在此处添加备注信息 * * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> * @site https://yourbatman.cn * @date 2021/6/9 10:36 * @since 0.0.1 */ @Slf4j @WebServlet(urlPatterns = "/corscookie") public class CorsCookieServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String requestURI = req.getRequestURI(); String method = req.getMethod(); String originHeader = req.getHeader("Origin"); log.info("收到请求:{},方法:{}, Origin头:{}", requestURI, method, originHeader); // 读取Cookie List<Cookie> myCookies = new ArrayList<>(); if (req.getCookies() != null) { myCookies = Arrays.stream(req.getCookies()).filter(c -> c.getName().equals("name") || c.getName().equals("age")).collect(toList()); } if (myCookies.isEmpty()) { // 种植Cookie Cookie cookie = new Cookie("name", "YourBatman"); // cookie.setDomain("baidu.com"); cookie.setMaxAge(3600); resp.addCookie(cookie); cookie = new Cookie("age", "18"); cookie.setMaxAge(3600); resp.addCookie(cookie); } else { myCookies.stream().forEach(c -> { log.info("name:{} value:{} domain:{} path:{} maxAge:{} secure:{}", c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getMaxAge(), c.getVersion(), c.getSecure()); }); } setCrosHeader(resp); resp.getWriter().write("hello cookie..."); } private void setCrosHeader(HttpServletResponse resp) { resp.setHeader("Access-Control-Allow-Origin", "http://localhost:63342"); } }
点击按钮,发送请求:
注意看,服务端代码虽然resp.addCookie(cookie);添加了Cookie,但是Response响应里并没有Set-Cookie这个头哦。查看浏览器发现木有Cookie:
也许你会说,当然没有啦,因为Response里没有Set-Cookie
头嘛,但我们代码里明明已经addCookie了呀。
这半截理论当然没问题,现在我在服务端程序里补充一个响应头:
private void setCrosHeader(HttpServletResponse resp) { resp.setHeader("Access-Control-Allow-Origin", "http://localhost:63342"); resp.setHeader("Access-Control-Allow-Credentials", "true"); }
重启服务端应用),再次发送请求,响应如下:
可以看到响应中已经有Set-Cookie响应头了,再次查看Cookie是否已被浏览器保存,同样的比比脸还干净:
浏览器没有存储Cookie。What?难道翻车了?No,下面教你如何解释以及怎么破?
跨域Cookie共享的关键点
这里要讨论的是跨域中Cookie的存储问题:默认情况下,浏览器是不会去为你保存下跨域请求响应的Cookie的。具体现象是:跨域请求的Response响应了即使有Set-Cookie响应头(且有值),浏览器收到后也是不会保存此cookie的。
要实现Cookie的跨域共享,有3个关键点:
- 服务端负责在响应中将Set-Cookie发出来(由Access-Control-Allow-Credentials响应头决定)
- 浏览器端只要响应里有Set-Cookie头,就将此Cookie存储(由异步对象的withCredentials属性决定)
- 浏览器端发现只要有Cookie,即使是跨域请求也将其带着(由异步对象的withCredentials属性决定)
为了满足这三个关键点,在实施层面就有三要素来指导我们开发来解决此类问题。
跨域Cookie共享的三要素
首先确保服务端能正确的在响应中有Set-Cookie响应头,这由Access-Control-Allow-Credentials: true来保证。因此服务端只需要做多加这一步即可:
resp.setHeader("Access-Control-Allow-Credentials", "true");
Access-Control-Allow-Credentials该头是可选的,是个bool值,它若为true就有两个作用:
- 在跨域请求的响应中允许Set-Cookie响应头
- 浏览器收到响应后,浏览器根据此头判断是否让自己的withCredentials属性生效
所以就来到了第二个要素:XMLHttpRequest对象的withCredentials属性。该属性是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。
var xhr = new XMLHttpRequest(); ... xhr.withCredentials = true;
Jquery的Ajax写法与此不同,但底层原理一样
官方的语言理解起来总是那么晦涩,翻译成人话:当异步对象设置了withCredentials=true时,浏览器会保留下响应的Cookie等信息,并且下次发送请求时将其携带。因此要指示浏览器存储Cookie并且每次跨域请求都携带,仅需加上此参数即可:
$.ajax({ url: "http://localhost:8080/corscookie", type: "GET", xhrFields: { withCredentials: true }, crossDomain: true });
以上两个要素完成后,影响“结果”的还有最后一个要素。这个要素比较隐晦,也是很多同学/文章忽略的点。
服务端的Access-Control-Allow-Origin这个响应头的值不能是通配符*
,而只能是具体的值。否则出现报错:
换句话讲:浏览器端跨域请求对象一旦开启withCredentials=true属性,服务端跨域Origin将不能再用*通配符,否则CORS error!
三要素都满足后(Access-Control-Allow-Credentials:true;Access-Control-Allow-Origin:http://localhost:63342;withCredentials=true),再次点击发送请求,结果如下:
完美。
总结
上篇文章对Cors进行了全面介绍,本文以跨域Cookie共享为场景,很好的对跨域知识点进行了补充,并且也补足了Cors里一个重要的响应头Access-Control-Allow-Credentials的解释,相信通过本文同学你能加深对Web中Cookie的了解,以及跨域情况下Cookie信息如何共享。
本系列下篇将着眼于跨域请求解决方案的阐述,欢迎关注。
本文思考题
本文已被https://yourbatman.cn收录。所属专栏:点拨-Cors跨域,后台回复“专栏列表”即可查看详情。
看完了不一定懂,看懂了不一定会。来,3个思考题帮你复盘:
- Access-Control-Allow-Origin值设置为通配符*是万金油吗?
- 如何通过Cookie技术实现SSO单点登录?
- 实现跨域Cookie共享的三要素是什么?