很早之前写过一篇关于Cookie和Session的文章,那是2017年的事咯,当时还是个学生,技术也菜,对知识理解的也不深。恰巧有机会重新学习Java Web,今天就再次来简单的聊一聊Cookie与Session。
1.会话与会话技术
在日常生活中,我们拨打电话接通后到挂断前,在这期间两人的交流就是一个会话。Web应用中的会话类似生活中的打电话,用户登录(拨号)、一系列的请求和响应(交流)、用户退出登录(挂断电话)。
我们在电话刚接通时(此手机无来电显示功能),首先会来一句,你好,我是XXX。两人互通暗号之后,确认了双方的身份,之后的交流也都是基于前面的信息来进行交流的,直到两人都完成了此次会晤的目的,挂断电话,此次会话结束。
但是我们知道,客户端和浏览器端通信使用的Http协议是无状态的,即每次请求对于服务端来讲都是一个新的请求,无法基于前面的信息进行交流,所以想象下,客户端和服务端两方的交流情形(每到第四步的时候就会拐回第一步)。
基于上面的两个对比,我们也可以知道,Web应用需要一种可以保持前面信息(之前的对话)的技术,这就是会话技术。客户端和服务器端每次的交流都可以被追踪,类似于电话里两人的交流,可以记住前面所说的话。
2.原有技术的不足
在我们之前学习的过程中,我们知道Java Web应用中是有域对象的,如HttpServletRequest、ServletContext,下面我们来下看这个两个域对象能否保存会话信息。
- HttpServletRequest:每次Http请求,Servlet容器就会创建一个ServletRequest对象,该对象中保存着此次请求的所传递的数据,并且还可以通过其绑定属性,来传递一些数据。但是,每次请求都会创建一个全新的ServletRequest,其生命周期为当前Http请求,如果会话过程中调用不同的接口,两次请求无法通过ServletRequest来共享信息。
- ServletContext:HttpServletRequest存在的问题,对于ServletContext来说似乎不是问题,因为ServletContext在整个应用中是全局共享的,因此调用不同的接口,是可以通过其来追踪之前的信息的。但是,如果如果多个用户同时来,似乎是个比较麻烦的事情。
基于ServletContext遇到的多用户问题,如果你用过redis做验证码或者其他缓存的话,其实多用户对你来说并不是问题,我们可以在需要保存的信息对应的key前增加唯一表示,比如用户的账号,形成一个如lizishu-code的key,这样即可解决多用户的问题。
当时这样来做,我们需要每次请求是都需要携带我们的账号信息,存在的问题主要有如下几点:
- 安全问题,如果只验证账号名,很容易被攻击,因此同时需要携带密码;
- 客户端如何保持账号信息,总不能从登录页开始,每个页面间的跳转都携带着账号和密码;
- 效率低,每次请求服务器都会默认来一遍身份验证;
原有技术无法有效的解决问题怎么办?OMG,当时是提出一个新技术了,也就是我们要讲的两位主角,Cookie和Session。Cookie用来解决客户端如何保存信息的问题,Session来解决多用户问题,即每个客户端会对应一个session,当前会话产生的信息可以保存在session中,使用Cookie和Session的关联性来解决安全问题。下面我们具体的来讲解。
3.Cookie的概念
Cookie是会话技术的一种,主要用于将会话过程产生的数据保存到客户端(浏览器),从而使客户端每次和服务端之间可以更好的进行交互。
我们通过一个生活中的例子来理解下Cookie的概念。在生活中,大家应该都有接触过会员卡,比如理发店、超市等等。我们办理会员卡后,店家会给我们一张卡片,以后每次来消费时,只需出示这张卡片,就可以获取到你的余额、积分、消费记录等信息,然后基于上面的信息来进行折扣的计算、积分的累加,余额的扣减等操作。
在Web应用中,Cookie的功能就类似于上面的例子中的会员卡,第一次请求时,会创建一个Cookie,当用户再次访问服务器时,就会携带上Cookie(会员卡),服务端也会根据处理结果,将一些信息放到Cookie中,以保存在客户端。
上图描述了Cookie在浏览器和服务器之间的传输过程。当用户第一次访问服务器时,服务器可以在响应信息(response)中增加Set-Cookie
响应头,将信息以Cookie为载体发送给浏览器。浏览器接收到服务器发送来的Cookie信息,就会将他保存在浏览器的缓冲区内。这样,当浏览器再次访问服务器时,就会将Cookie放在请求消息中,Web服务器就可以通过request中的用户信息来分辨此次请求是由哪个用户发起的。
从上面我们可以发现,服务器端可以通过HttpServletRsponse来向客户端发送Cookie,那浏览器只能缓存服务器端发来的Cookie么?答案当时是NO,浏览器也可以通过JS来创建Cookie对象,并将其存储。下面我们分别来介绍这两种方式。
4.服务器端的Cookie
为了封装Cookie信息,Tomcat在Servlet Api中提供了一个Cookie类,其中提供了许多方法,让我们可以方便快捷的创建Cookie和设置其属性。
首先我们来看下Cookie的构造方法,Cookie类只提供了一个构造方法,其源码如下所示:
public Cookie(String name, String value) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException(
lStrings.getString("err.cookie_name_blank"));
}
if (!isToken(name) ||
name.equalsIgnoreCase("Comment") || // rfc2019
name.equalsIgnoreCase("Discard") || // 2019++
name.equalsIgnoreCase("Domain") ||
name.equalsIgnoreCase("Expires") || // (old cookies)
name.equalsIgnoreCase("Max-Age") || // rfc2019
name.equalsIgnoreCase("Path") ||
name.equalsIgnoreCase("Secure") ||
name.equalsIgnoreCase("Version") ||
name.startsWith("$")) {
String errMsg = lStrings.getString("err.cookie_name_is_token");
Object[] errArgs = new Object[1];
errArgs[0] = name;
errMsg = MessageFormat.format(errMsg, errArgs);
throw new IllegalArgumentException(errMsg);
}
this.name = name;
this.value = value;
}
从上面我们可以看到,创建一个Cookie对象需要传入两个参数name,value,也是我们比较熟悉的Key-Value模式,并且name不能为null或空字符串,不可等于保留的token名。需要注意的是,Cookie对象一旦创建,其name属性就不可更改了,value可以被修改。
下面我们来看下Cookie类提供的设置其属性的方法:
下面我们对其几个重要的属性进行讲解:
- maxAge:表示此Cookie在客户端的有效期,单位是秒。默认值为-1,当整个浏览器关闭后,此Cookie失效;当maxAge值为正数时,即此Cookie还剩余多少秒的有效期;当值为0时,此Cookie失效,浏览器进行删除操作;
- path:表示此Cookie对path下的所属的目录和子目录有效,比如path设置为
/rest
,放客户端发起/rest/aaaServlet请求时,就会携带上此Cookie;如果想让此Cookie对站点所有的目录有效的话,可以设置为/
; - domain:表示可以访问此Cookie的域名,如果不设置的话,会根据当前请求url来设置;当我们手动设置时,可以采用以".“开头来定义此Cookie更大的域名访问范围,比如设置domain为”.csdn.net",则当访问时,一样可以使用此”.csdn.net"下的cookie(当然这里还需要Cookie的path同时满足要求);
下面我们来简单的演示下服务器端是如何向客户端(浏览器)发送Cookie对象的,我们新建一个CookieServlet,urlPattern默认,其中的doGet方法如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置编码方式
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
Cookie cookie = new Cookie("name", "lizishu");
//xx.localhost域名下的所有应用都可以使用此Cookie
cookie.setPath("/");
cookie.setDomain(".localhost");
//有效期5min
cookie.setMaxAge(300);
response.addCookie(cookie);
}
浏览器中输入URL:http://localhost:8080/FirstProject/CookieServlet,访问此Servelt后,我们打开Chrome的控制态F12->Application->Storage->Cookies->http://localhost:8080(根据你输入的url来定),截图如下:
在浏览器中存在Cookie后,我们随便在浏览器中在进行一次请求,http://localhost:8080/FirstProject/xxxx,我们来看下其请求头,因为xxxx没有对应的目录,因此404,不过这个不是重点,重点是,此次请求中Request Headers中已经携带了浏览器中存储的Cookie。
在服务器端如何获取Cookie
5.浏览器端的Cookie
Cookie的创建工作除了在服务器端,还可以在浏览器端通过JavaScript完成。下面我们来看下如何使用JavaScript来来创建Cookie和修改Cookie(这里仅仅是为了演示,因此使用原生的Js,Jquery等其他JS框架都有对Cookie的支持)。
首先我们来看下JavaScript是如何操作Cookie的:
//创建一个Cookie,属性默认
document.cookie="password=123456";
//创建一个Cookie,设置属性:过期时间,path
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2021 12:00:00 GMT; path=/";
//读取Cookie,返回name1=value1;...;namen=valuen 形式的字符串
document.cookie;
//修改Cookie,重新创建一遍,name相同会覆盖之前Cookie,修改了过期时间
document.cookie="attribute=pathDomain; expires=Thu, 14 Dec 2020 12:00:00 GMT; path=/";
//删除Cookie,可以指定过期时间为当前时间;注意:因为过期时间以浏览器的服务器时间为准,一般会有八小时时差
document.cookie="password=123; expires=" + new Date();
上面即是我们的原生JS操作Cookie的示例代码,我们将其在Chrome的控制台中运行,执行过程如下图所示:
我们来看下Application中的Cookie信息。
下面提供两个简单的Cookie操作的JS函数:
//创建Cookie,并设置有效期(单位天)
function setCookie(cname,cvalue,exdays)
{
var d = new Date();
d.setTime(d.getTime()+(exdays*24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}
//获取对应Cookie的值,通过字符串截取的方式
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
//删除Cookie,过期时间提前1天,解决时差问题
function delCookie(cname)
{
var d = new Date();
d.setTime(d.getTime()-(24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=; " + expires;
}
6.总结
因为篇幅问题,本文只对Cookie进行讨论,下文会对Session进行详细的讨论。Cookie的出现让浏览器保存会话信息变得非常方便,而浏览器发起Http请求时,会将所有当前请求可用的Cookie全部带上,也大大的方便了程序员的开发工作。
又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。
Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。
有任何疑问,可以评论区留言。