Http是无状态的协议,客户端每次对服务端的http请求都是独立的,不受该客户端其它的请求的影响。
为了把Http这个无状态协议变的与上下文有关系,我们引入了会话(Session)的概念,具有相同会话ID的请求使之变成了有状态。
服务端可以给请求setSession的信息,信息保存在服务端内存,同时在response时将session内容推送给客户端浏览器,浏览器为了保存SessionID等信息,又有了Cookie这玩意,Cookie本质是一块存储少量数据的存储空间,可以存到内存也可以写入磁盘。每次浏览器向一个域名发送http请求时会去查找该域名的Cookie信息拼接到Http的header中送到Server端。
谈到域名,可以简单的理解为我们访问的地址(请注意不是真正映射成的IP地址),而Cookie具有不跨域性质,只会将属于该域名的Cookie信息添加到Header中传到Server端。
下面我们写个Demo来试下Cookie的跨域
先设置下本机的host,添加两个域名peer1和peer2。
用管理员权限打开C:\WINDOWS\system32\drivers\etc\hosts
添加两行:
127.0.0.1 peer1
127.0.0.1 peer2
测试代码放在了 git 上:https://github.com/yejingtao/forblog/tree/master/cookie-domain
核心代码如下:
@Value("${cookie.value}")
String cookieValue;
@RequestMapping("/setCookie")
public String home(HttpServletResponse response,HttpServletRequest request) {
Cookie cookie = new Cookie("testName", cookieValue);
response.addCookie(cookie);
return "Success";
}
@RequestMapping("/getCookie")
public String home(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
Cookie[] cookies = request.getCookies();
if(cookies!=null) {
for(Cookie cookie : cookies) {
sb.append(cookie.getName());
sb.append(",");
sb.append(cookie.getValue());
sb.append(";");
}
}
return sb.length()==0?"Empty":sb.toString();
}
测试前先看下我本机的cookie有哪些内容,需要对peer1和peer2这两个域名做下清理:
以Chrome浏览器为例:
设置--高级—内容设置—cookie—查看所有cookie和网站数据,保证不要有peer1和peer2的cookie数据。
浏览器请求http://peer1:8087/setCookie,会发现cookie里多了peer1
打开箭头可以看到详细的cookie信息,cookie的属性有我们代码中显式设置的,也有隐式默认的:
此时我们请求peer1的getCookie是可以获取到cookie值的,请求peer2的getCookie是获取不到cookie内容的,所以验证了cookie是不能跨域的,而这个域是浏览器请求的域名,哪怕他们最终的服务端是一起的也不可以跨越。
上图中cookie值里有个域名peer1,这个不是我们代码中显式设置的,浏览器默认帮我们根据域名设置的,那么如果我显式设置一个跟域名对不上的domain会如何呢?
给setCookie代码增加一行:
@RequestMapping("/setCookie")
public String home(HttpServletResponse response,HttpServletRequest request) {
Cookie cookie = new Cookie("testName", cookieValue);
cookie.setDomain("peer2");
response.addCookie(cookie);
return "Success";
}
清理浏览器cookie、重启服务后访问peer1的setCookie
奇怪的是无论是peer1的getCookie还是peer2的getCookie都获取不到cookie内容,而且设置里面也确实没有。难道是浏览器自己的安全机制发现我set的Domain与域名不匹配所以认为这是个无效的cookie就没有保存?调用下peer2的setCookie就可以验证这一猜想,测试结果是肯定的。
Cookie是不安全的,浏览器、脚本类语言、甚至直接访问cookie保存文本的地址都可以获取到Cookie信息,所以尽量不要讲敏感内容保存在cookie中,即便是密码之类的可以进行加密,但是别人可以不需要解密直接使用你cookie中的内容进行欺骗。所以在服务端对cookie使用时,尽量设置超时时间,客户端尽量cookie不使用时(例如浏览器关闭)及时清空。
Cookie是客户端保存的内容,Session是服务端保存的内容,Session对于单个客户来说是安全的,而Cookie是客户共享的。所以可以推断得出从安全角度讲能用Session的情况尽量不要用Cookie,特别是现在Redis等共享缓存组件的使用使服务端存储Session的能力大大加强。
最后回到本文最开始的那张图,我们将代码再修改下验证下第一张图。
代码修改如下:
@RequestMapping("/setCookie")
public String home(HttpServletResponse response,HttpServletRequest request) {
request.getSession().setAttribute("sessionID", "haha");
return "Success";
}
请求后浏览器会保留一个cookie来存取sessionID