Cookie&Session
学习摘要:
今天主要是讲解Cookie和Session的问题。
重点深入理解HTTP协议的无状态指的是什么?以及无状态带来了什么问题。什么是一次会话?在一次会话中,如何解决保存产生的会话信息。
重点掌握什么是Cookie?学习Cookie能解决什么问题?重点理解并掌握Cookie的运行原理是什么?重点掌握Cookie的使用。掌握Cookie的优缺点是什么?了解Cookie设置路径的作用,和设置域的作用。了解Cookie在实际开发中的应用场景。
重点掌握什么是Session?思考学习了Cookie为何还要学习Session?重点掌握Session的运行原理是什么?掌握Session的使用。掌握并理解Session的优缺点是什么?理解URL重写的作用。对比一下Cookie和Session,搞清楚它们之间的区别和联系。
HTTP协议的无状态连接
HTTP无状态协议指的是什么?
百度百科上的解释:
HTTP无状态协议,是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
总结直白的话:
HTTP有一个特点:无状态连接.服务端不知道上一次是哪一个客户端请求了自己.
无状态连接带来的问题
在一次会话中,我们可以查看多个资源,每一个资源都会先发送请求,再响应,每次的请求都是客户端发出的.但是,HTTP是无状态的,它不知道上一次是谁请求了自己.也就是说,在一次会话中,多个请求之间无法共享数据,无法跟踪用户的会话信息.
什么是一次会话?
可以简单的理解:打开一个浏览器,访问某一个站点,在该网址内部查看信息,点击超链接等相关的操作,最后关闭浏览器的整个过程,称之为一次会话.
最后再通过一个图来说明一次会话和HTTP协议的无状态连接的概念。如:

看图分析:图中的人物,大爷就相当于服务端,夏洛就相当于客户端。服务端是很健忘的。他们之间的对话称为是一次会话。
无状态连接带来的问题,如何解决呢?通过一个查看邮件的案例,来寻找最佳解决方案
案例的需求分析:
定义一个登陆页面(login.jsp),通过点击登陆按钮,跳转到ListServlet。在该Servlet中通过response动态响应一个邮件列表的数据到浏览器上进行显示。点击邮件二,跳转到ContentServlet,在该Servlet中通过response动态响应该邮件的内容到浏览器上进行显示。
案例的流程图:

案例的代码:
login.jsp:
<body>
<form action="/param/list" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="pwd"><br/>
<input type="submit" value="登陆">
</form>
</body>
ListServlet:
@WebServlet("/param/list")
public class ListServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = req.getParameter("username");
writer.println("欢迎:" +username+"<br/>");
writer.println("<a href='/param/content'>邮件一</a><br/>");
writer.println("<a href='/param/content'>邮件二</a><br/>");
writer.println("<a href='/param/content'>邮件三</a><br/>");
}
}
ContentServlet:
@WebServlet("/param/content")
public class ContentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = req.getParameter("username");
writer.println("欢迎:" +username+"<br/>");
writer.println("恭喜您,于2018年12月12日至2019年6月12日在叩丁狼完成了全部课程,成绩合格,准予毕业");
}
}
运行结果:

通过测试发现在ContentServlet中获取用户名是失败的。这是为何呢?
因为是通过超链接的方式跳转到ContentServlet中的,也就是get的请求方式。 在get请求中没有拼接用户名信息,所以在ContentServlet中是没办法通过req.getParameter("username");来获取用户信息的。
那么我们可以采取在超链接中拼接参数的方式把用户名携带过去。如:
String username = req.getParameter("username");
writer.println("欢迎:" +username+"<br/>");
writer.println("<a href='/param/content?username="+username+"'>邮件二</a><br/>");
分析:使用参数的传递机制.在每一个请求之间使用参数来传递需要共享的数据. 可以解决问题,但是请求需要共享的数据全部都暴露在浏览器的地址栏中,不安全.
解决思路:
那么我们解决这个问题的切入点就是想办法共享数据而不暴露在浏览器地址栏。要想解决共享数据不暴露在浏览器地址栏,那首先搞清楚,使用参数的传递机制,参数为何会在浏览器地址栏显示,通过抓包工具发现,请求的参数是拼接在请求的地址中的,而get请求的地址是在请求行中的。 如:
GET /param/list?username=admin HTTP/1.1
那么如果把请求的参数不放入请求行,而是放在请求头中,通过请求头把参数带过去,这样不就解决了吗
如何才能把请求的参数放在请求头中呢?
要想解决这个问题,就要学习Cookie 和 Session 技术,这两个技术都可以把请求参数的内容设置在请求头中。
Cookie
课前引导为何要学习Cookie?
因为http的请求是无状态的。客户端与服务器在通讯的时候,是无状态的,其实就是客户端在第二次来访的时候,服务器根本就不知道这个客户端以前有没有来访问过。每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。为了更好的用户体验,更好的交互,所以使用cookie技术来保存会话中的共享数据
什么是cookie呢?
Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
Cookie是客户端技术,服务端的程序把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。
cookie的工作原理是什么?
Cookie是由服务端生成的,发送给客户端(通常是浏览器)的。
- 创建Cookie
当用户第一次浏览某个使用Cookie的网站时,该网站的服务器就进行如下工作:
①为该用户生成一个唯一的识别码(Cookie id),创建一个Cookie对象;
②默认情况下它是一个会话级别的cookie,存储在浏览器的内存中,用户退出浏览器之后被删除。
③将Cookie放入到HTTP响应报头,将Cookie插入到一个 Set-Cookie HTTP请求报头中。
④发送该HTTP响应报文。
- 设置存储Cookie
浏览器收到该响应报文之后,根据报文头里的Set-Cookied特殊的指示,生成相应的Cookie,保存在客户端。该Cookie里面记录着用户当前的信息。
- 发送Cookie
当用户再次访问该网站时,浏览器首先检查所有存储的Cookies,如果某个存在该网站的Cookie(即该Cookie所声明的作用范围大于等于将要请求的资源),则把该cookie附在请求资源的HTTP请求头上发送给服务器。
- 读取Cookie
服务器接收到用户的HTTP请求报文之后,从报文头获取到该用户的Cookie,从里面找到所需要的东西。
通过一个图在来理解cookie的工作原理:

看图分析,浏览器端第一次发送的请求是没有携带cookie的,因为这个时候还没有cookie,等服务器响应回来的时候会通过Set-Cookie属性,分配浏览器一个cookie。浏览器下次再发送请求到服务器的时候会通过Cookie属性把之前服务器分配的cookie内容带过去。
我们在通过一个生活中的例子来结束cookie工作原理的讲解:
去食堂吃饭办理饭卡的例子:
- 第一次去学校食堂吃饭,需要办理饭卡(学校食堂只能刷卡)
- 餐厅的老板(服务端),就会创建一个饭卡(cookie)并在卡里充值了金额
- 我给他钱,餐厅的老板(服务端)把卡交给了我,此时我拥有了饭卡(cookie)
- 以后每次去吃饭,我都带着饭卡(cookie),餐厅的老板(服务端)获取饭卡(cookie)中的金额,进行扣除饭钱。
cookie的入门使用
通过上面的cookie的工作原理,得知其实cookie就是操作共享数据的一个载体。那么操作数据,无非就是对数据的增删改查操作。
所以关于cookie的使用,其实也就是对数据的增删改查。
要使用cookie来操作数据的步骤:
1). 创建一个cookie对象。并通过构造器设置共享的数据.
Cookie cookie = new Cookie(String name,String value);
参数:
name: 该当前Cookie取一个唯一的名字.
value: 存储在Cookie的共享数据,只能是String类型.
如:
Cookie cookie = new Cookie("currentName","tony");
2). 把Cookie放入响应中,响应给浏览器,把共享的数据存储在浏览器中.
response.addCookie(cookie);
3). 获取Cookie以及获取Cookie中的数据.因为Cookie存在在请求头中,所以应该通过request去获取.
Cookie[] cs = req.getCookies();
获取当前Cookie的名字: String name = cookie对象.getName();
获取当前Cookie的值: String value= cookie对象.getValue();
为何是一个数组呢,因为可以存储很多cookie。
接下来通过cookie来改造查看邮件的案例。代码如下:
login.jsp:
<body>
<form action="/cookie/list" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="pwd"><br/>
<input type="submit" value="登陆">
</form>
</body>
ListServlet:
@WebServlet("/cookie/list")
public class ListServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = req.getParameter("username");
//==========================================
// 创建一个cookie对象,并赋值
Cookie cookie = new Cookie("currentName", username);
// 把cookie 存入到浏览器端
resp.addCookie(cookie);
//==========================================
writer.println("欢迎:" + username + "<br/>");
writer.println("<a href='/cookie/content'>邮件一</a><br/>");
writer.println("<a href='/cookie/content'>邮件二</a><br/>");
writer.println("<a href='/cookie/content'>邮件三</a><br/>");
}
}
ContentServlet:
@WebServlet("/cookie/content")
public class ContentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = "";
//==========================================
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
if(cookieName.equals("currentName")){
username = cookie.getValue();
break;
}
}
}
//==========================================
writer.println("欢迎:" + username + "<br/>");
writer.println("恭喜您,于2018年12月12日至2019年6月12日在叩丁狼完成了全部课程,成绩合格,准予毕业");
}
}
运行结果:

我们通过把用户名称保存到cookie中,在邮件内容页面获取cookie中的用户名信息,最终达到显示的目的。
最后我们再通过抓包工具来的对比一下使用cookie以后和不使用cookie,请求头的内容的变化。

使用Cookie的一些细节问题
1). 关于Cookie存入中文的问题。
Cookie在Tomcat8.5之前name和value都不支持中文。那么如果想把中文存入Cookie,怎么办呢?我们可以通过URLEncoder进行编码,再存入,在获取的时候再通过URLDecoder进行解码。如:
public static void main(String[] args) throws Exception{
String msg = "叩丁狼";
String encode = URLEncoder.encode(msg, "utf-8");
System.out.println("encode = " + encode);
String decode = URLDecoder.decode(encode, "utf-8");
System.out.println("decode = " + decode);
}

2). 关于Cookie的生命周期的问题。
通过查看API,Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。
文档如下:
public void setMaxAge(int expiry)
Sets the maximum age of the cookie in seconds.
A positive value indicates that the cookie will expire after that many seconds have passed. Note that the value is the maximum age when the cookie will expire, not the cookie's current age.
A negative value means that the cookie is not stored persistently and will be deleted when the Web browser exits. A zero value causes the cookie to be deleted.
关于setMaxAge方法的介绍大概意思如下:
参数expiry 是以秒为单位的。如果设置expiry为正数,则表示cookie将在该expiry秒之后过期。如果设置expiry为负值意味着cookie不是持久存储的,将在Web浏览器退出时删除。如果设置expiry为零值会导致cookie被删除。
Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie:
Cookie的分类(会话Cookie和持久化Cookie):
A. 内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。默认值
如果在服务器端没有调用setMaxAge方法设置cookie的有效期,那么cookie的有效期只在一次会话过程中有效,用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一次会话,当用户关闭浏览器,会话就结束了,此时cookie就会失效.
B.硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。
如果在服务器端使用setMaxAge方法设置了cookie的有效期,比如设置了30分钟,那么当服务器把cookie发送给浏览器时,此时cookie就会在客户端的硬盘上存储30分钟,在30分钟内,即使浏览器关了,cookie依然存在,在30分钟内,打开浏览器访问服务器时,浏览器都会把cookie一起带上,这样就可以在服务器端获取到客户端浏览器传递过来的cookie里面的信息了
总结:
设置Cookie的最大存活时间:
cookie对象.setMaxAge(int seconds);
seconds == 0: 删除Cookie.
seconds < 0: 内存Cookie.
seconds > 0: 硬盘Cookie,存储指定的秒数.
3). 关于Cookie存入的内容的修改问题。
修改Cookie中指定属性名的属性值.
需求:修改Cookie cookie = new Cookie("currentName","vincent");
方式1:创建一个同名的新的Cookie.
Cookie c = new Cookie("currentName","tony");
response.addCookie(c);// 不能忘记
方式2:获取该Cookie对象,通过setValue方法,重新设置新的value值.
c.setValue("新的值");
response.addCookie(c); // 不能忘记
注意:别忘了,重新把该Cookie放入响应中:response.addCookie(cookie);
4). 关于Cookie的删除问题。
前面了解了cookie的生命周期,所以如果要想删除cookie,只需要设置MaxAge的值为0即可。cookie对象.setMaxAge(0); 如:
// 新建Cookie
Cookie cookie = new Cookie("username","tony");
// 设置生命周期为0,不能为负数
cookie.setMaxAge(0);
// 响应给浏览器,必须执行这一句
response.addCookie(cookie);
5). 关于Cookie的路径。(了解)
一个Servlet的映射路径 如果是/list, 那么它的cookie的存储路径是/ ;如果是/cookie/list,那么它的cookie的存储路径是/cookie 。 这个路径称为path。
如:在同一个服务器上有目录如下
Servlet1("/cookie/list"): Servlet2("/abc/content"):
在Servlet1中保存创建cookie,并保存到浏览器中,此时path是/cookie。
此时Servlet1中的Cookie是不会请求发送给Servlet2,因为路径不同.
此时,Servlet1,只会把Cookie传给以/cookie/打头的资源.
我们如果想上面的Servlet1 和 Servlet2 之间共享数据怎么处理呢?通过查看API,发现可以设置path路径。 如:cookie对象.setPath("/"); 这样一设置就表示存储cookie所在的目录,就是根目录。这样同一个服务器上的所有path都可以访问了。
6). 关于Cookie的域名。(了解)
Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。
我们一般如果有很多个子项目,但是在实际开发中,我们一般在一个子项目中登陆了,那么在其他子项目中,直接显示登陆状态,而无从再登陆。我们会使用一级域名相同,二级域名不同的方式。例如:music.baidu.com、map.baidu.com、tieba.baidu.com,它们的域名不同,正常情况下,也不能交互使用Cookie,因为二者的域名并不严格相同。但百度希望它们之间可以共享Cookie,这样只需要登陆一次即可。 如果想让cookie跨域访问呢?通过查看API,发现有个setDomain方法,可以设置cookie的域。
public void setDomain(java.lang.String pattern)
Specifies the domain within which this cookie should be presented.
The form of the domain name is specified by RFC 2109. A domain name begins with a dot (.foo.com) and means that the cookie is visible to servers in a specified Domain Name System (DNS) zone (for example, www.foo.com, but not a.b.foo.com). By default, cookies are only returned to the server that sent them.
关于该方法的介绍:
大概是:域名的形式由RFC 2109指定。域名以点(.foo.com)开头,这意味着在指定的域名系统(DNS)区域(例如,www.foo.com,而不是a.b.foo.com)中的服务器可以看到cookie。默认情况下,cookie只返回给发送cookie的服务器。
所以解决百度子项目中的cookie共享的方法:
1).设置Cookie的domain,例如:cookie.setDomain(".baidu.com")
2).设置Cookie的path为“/”,例如:cookie.setPath("/");在整个baidu.com中都能传递.
Cookie在实际开发中的应用场景
许多网站上都有新用户注册这一项,有时注册了一下,等到下次再访问该站点时,会自动识别到你,并且向你问好,是不是觉得很亲切?当然这种作用只是表面现象,更重要的是,网站可以利用cookies跟踪统计用户访问该网站的习惯,比如什么时间访问,访问了哪些页面,在每个网页的停留时间等。利用这些信息,一方面是可以为用户提供个性化的服务,另一方面,也可以作为了解所有用户行为的工具,对于网站经营策略的改进有一定参考价值。
Cookie的根本作用就是在客户端存储用户访问网站的一些信息。典型的应用有:
1、记住密码,下次自动登录。
2、购物车功能。
3、记录用户浏览数据,进行商品(广告)推荐。
对于第一个应用场景,在这里阐述一下:
登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。
保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。
还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。
这几种方案验证账号时都要查询数据库。
还有一种比较好的方案。就是只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。
如:把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。
对于第二个应用场景,在这里阐述一下:
实现购物车的方案有三种:
1.用cookie实现购物车;
2.用session实现购物车;
3.用cookie和数据库(购物车信息持久化)实现购物车;
分析一下这三种方法的优缺点:
1.单纯有cookie实现购物车,这样的购物车不是很理想,设想一下,如果客户端的浏览器把cookie给禁用了,这种方法不是很好。
2.session中保存购物车的信息,这个只是在一个会话中可用,如果用户没有登录,或者说登录了以后,添加购物车,在关闭浏览器或者登出后,之前所添加的购物车通通没有了。所以这种方案也不是最优的。
3.使用Cookie + 数据库的方案。是最佳方案,为何呢?我们先来看一下使用这种方案的实现流程:
-
A. 用户登录前的数据流:用户在没有登录系统的时候,对喜欢的商品进行添加购物车,那么这个时候,我们可以把购物车信息保存到cookie中,这里会涉及到cookie的添加,修改操作;也即如果之前在cookie中不存对应的cookie,则就对cookie进行添加操作。如果在cookie中存在对应的cookie,那么,这时候,就要对cookie进行修改操作了(这里涉及到用户对同一个商品进行多次添加购物车的情况)。
-
B.用户登录后的数据流:用户在登录后,系统首先做的第一件事就是去获取对应的cookies,如果存在相关的购物车cookies,那么就对该购物车信息进行相应用户User的持久化操作,要么添加,要么修改。(添加操作:该用户所对应的购物车如果没有相应的信息进行添加操作;修改操作:类似的,如果存在对应用户的购物车信息,就进行修改操作)。用户登录后,也可以进行购物车的添加操作,不过,这里不是添加到cookie中,而是直接持久化到数据库中。注:用户登录后的数据都是和数据库打交道。
对于第三个应用场景,在这里阐述一下:
当你在浏览网站的时候,WEB 服务器会先送一小小资料放在你的计算机上,Cookie 会帮你在网站上所打的文字或是一些选择,都纪录下来。当下次你再光临同一个网站,WEB 服务器会先看看有没有它上次留下的 Cookie 资料,有的话,就会依据 Cookie里的内容来判断使用者,送出特定的网页内容给你。 Cookie 的使用很普遍,许多有提供个人化服务的网站,都是利用 Cookie来辨认使用者,以方便送出使用者量身定做的内容,像是 Web 接口的免费 email 网站,都要用到 Cookie。
注意:Cookie功能需要浏览器的支持。如果浏览器不支持Cookie(如大部分手机中的浏览器)或者把Cookie禁用了,Cookie功能就会失效。不同的浏览器采用不同的方式保存Cookie。
Cookie的缺陷
1):多个人使用同一台电脑的时候,可以查看浏览器的Cookie,不安全.
2):Cookie存储中文比较麻烦(需要通过URLEncoder编码,通过URLDecoder再解码).
3):Cookie的value是String类型,一个Cookie就只能存储一个数据,并且只能存储字符串,如果需要存储多个数据,就得死还有N个Cookie.
4):一个站点对Cookie有限制:浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
Session
课前引导为何要学习Session?
由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。那么问题来了,既然无状态,那完成一套完整的业务逻辑,发送多次请求的情况数不胜数,使用http如何将上下文请求进行关联呢?会话跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,并且弥补了使用Cookie存入存在的一些弊端。使用Cookie记录的一些弊端,比如:存入中文过于麻烦,传输数据不安全。只能存入一个字符串数据。大小限制4KB等。
所以我们要学习Session(服务端的会话技术)。
Session是什么?
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
Session的工作原理
当用户访问到服务器,在访问的资源中调用了getSession方法,服务器就要为该用户创建一个Session对象,在创建这个Session的时候,服务器首先检查这个用户发来的请求里是否包含了一个JSESSIONID,如果包含了一个JSESSIONID则说明之前该用户已经登陆过并为此用户创建过Session,那服务器就按照这个JSESSIONID把这个Session在服务器的内存中查找出来(如果查找不到,就有可能为他新创建一个),如果客户端请求里不包含有JSESSIONID,则为该客户端创建一个Session并生成一个与此Session相关的JSESSIONID。这个JSESSIONID是唯一的、不重复的、不容易找到规律的字符串,这个JSESSIONID将被在本次响应中返回到客户端保存,而保存这个JSESSIONID的正是Cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
通过一个图在来理解session的工作原理:

看图分析,浏览器端第一次发送请求给服务器,服务器端通过调用getSession方法,创建一个Session对象并生成一个字符串,在响应给浏览器的时候,把生成的字符串包装成key=value的形式(使用JSESSIONID作为key,生成的字符串内容作为value)通过Set-Cookie属性返回给浏览器,浏览器把服务器响应的字符串(JSESSIONID)保存到内存cookie中。 再次请求服务器的时候把这个JSESSIONID 带过去。在服务器端根据JSESSIONID查找对应的Session对象。
我们在通过一个生活中的例子来结束session工作原理的讲解:
去超市消费办理会员卡的例子:
- 第一次去超市消费,办理会员卡(可以积分,积分可以兑换商品)
- 超市的老板(服务端),就会创建一个会员卡(Session)并在卡中记录了用户的手机号(JSESSIONID),并把会员卡的信息存在服务器中。
- 下次去超市消费,结账的时候,我只是报自己的手机号(JSESSIONID),并没有带会员卡(Session)
- 超市的老板收到我的手机号以后(JSESSIONID),去服务器上查找对应的会员卡信息。找到对应的会员卡(Session),在里面的积分的基础上增加本次的积分。
Session的入门使用
通过上面的Session的工作原理,得知和上面学习的Cookie一样。Session也是操作共享数据的一个载体。那么操作数据,无非就是对数据的增删改查操作。
所以关于Session的使用,其实也就是对数据的增删改查。
要使用Session来操作数据的步骤:
1:创建和获取Session对象.
HttpSession session = request.getSession(true);如果当前请求中存在一个Session对象,就直接返回,如果不存在Session对象,就先创建一个再返回.
HttpSession session = request.getSession(false);如果当前请求中存在一个Session对象,就直接返回,如果不存在Session对象,就返回null.
HttpSession session = request.getSession();
等价于
HttpSession session = request.getSession(true);
2:往Session中存储数据.
session对象.setAttribute(String name,Object value);
3:从Session中取出数据.
Object value = session对象.getAttribute(String key);
4:删除Session(用户注销登陆).
1):删除Session中指定属性名的值.
session对象.removeAttrbute("currentName");
2):销毁Session对象(Session中所有的属性都不存在).
session对象.invalidate();
5.Session的超时管理
在超时时间之内,如果客户端和服务端没有交互(用户的两次操作之间不能超过该时间),则自动的销毁Session.
session对象.setMaxInactiveInterval(60 * 10);//超过10分钟,销毁Session.
Tomcat服务器的默认超时时间为:30分钟,Tomcat一般在20多分钟就销毁了.
接下来通过Session来改造查看邮件的案例。代码如下:
login_session.jsp:
<body>
<form action="/session/list" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="pwd"><br/>
<input type="submit" value="登陆">
</form>
</body>
ListServlet:
@WebServlet("/session/list")
public class ListServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = req.getParameter("username");
//=========================================
// 创建一个Session对象
HttpSession session = req.getSession();
// 往session中存入用户信息
session.setAttribute("USER_INFO_IN_SESSION",username);
//=========================================
writer.println("欢迎:" +username+"<br/>");
writer.println("<a href='/session/content'>邮件一</a><br/>");
writer.println("<a href='/session/content'>邮件二</a><br/>");
writer.println("<a href='/session/content'>邮件三</a><br/>");
}
}
ContentServlet:
@WebServlet("/session/content")
public class ContentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
String username = "";
//=========================================
// 从session中获取用户信息
username = (String) req.getSession().getAttribute("USER_INFO_IN_SESSION");
//=========================================
writer.println("欢迎:" +username+"<br/>");
writer.println("恭喜您,于2018年12月12日至2019年6月12日在叩丁狼完成了全部课程,成绩合格,准予毕业");
}
}
运行结果:

我们通过把用户名称保存到Session中,在邮件内容页面获取Session中的用户名信息,最终达到显示的目的。
最后我们再通过抓包工具来的对比一下使用Cookie和使用Session,请求头的内容的变化。

通过上图的对比发现。Session是基于Cookie工作的。这个JSESSIONID将在响应中返回到客户端保存,而保存这个JSESSIONID的正是Cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。我们可以在浏览器的选项中把Cookie禁止,那么如果把客户端的Cookie禁止了,JSESSIONID还能再用了吗?通过实验来验证一下。
以Chrome为例禁用Cookie:

禁用Cookie以后再来测试使用Session的方式编写的邮件的案例:

如果用户在浏览器端禁用了Cookie,那么我们就没有办法使用Session了吗?该如何解决呢?
解决的思路:
要想解决这个问题,首先是要搞清楚,问题的根源在哪里?通过上面的实验我们得知Session无法使用的根源就在于JSESSIONID没有传给服务器。而之前的传输是通过Cookie来完成了。如果Cookie被禁用了。JSESSIONID就传输不过去了,所以我们的解决的问题,就是不使用Cookie的情况下,如何把JSESSIONID传给服务器的问题。那么我们可以采用把JSESSIONID类似使用传参数的形式拼接在请求的地址中,(注意是类似传参数的方式,但是拼接符使用的是分号而不是问号),来进行传输。如:
String sessionId = session.getId();
writer.println("<a href='/session/content;jsessionid="+sessionId+"'>邮件一</a><br/>");
这样做问题是解决了,但是也有个困惑。就是不知道浏览器端是否禁用了Cookie,如果禁用了Cookie,我们可以使用上述方式,但是如果没有禁用,那么再使用上述方式,再传入jsessionid不就多余了吗。
所以有没有提供一种智能的方式,客户端禁用就拼接,不禁用就不拼接。那么就是使用URL重写可以做到这一点。
String url = response.encodeURL("/session/list");
//自动的在资源之后拼接;jsessionid=872870F9466CE7B3A11FD3B768FDD684
注意:开发中一般都不会禁用Cookie的.
使用Session的细节
-
1:一般的,我们存储到Session中的属性名称,要唯一,我们习惯XXX_IN_SESSION:
session对象.setAttribute("USER_ IN_ SESSION","will"); -
2:若需要把多个数据存放到Session中,就得调用setAttribute方法N次,可以的.
一般的,我们把需要存储的数据,封装成一个对象,然后在存储到Session中.
把用户的信息,封装到user对象.
session对象.setAttribute("USER_IN_SESSION",user对象); -
3:如果多台服务器之间需要共享Session,此时Session中的对象,一般实现java.io.Serializable(才能在网络上传输).
序 列 化: 把对象信息存储为二进制.
反序列化: 把二进制信息恢复成对象.
public class User implements java.io.Serializable{....}
Session在实际开发中的应用场景
登录验证信息
当用户登录后,可以把用户的信息放到session,然后再需要验证的页面中获取用户信息,如果为null,说明该用户非法,强制返回用户登录页面去。
使用Cookie和Session完成一个综合案例
案例需求:
使用Cookie和Session完成一个登陆和退出登陆的功能(包含增加是否记住用户名和密码选项)。
案例需求分析:
编写一个登陆页面 login.jsp 点击登陆按钮, 把账号和密码发送给 LoginServlet 校验结果 如果登陆成功跳转到主页main.jsp 如果登陆失败回到登陆页面login.jsp
如果在登陆页面上对是否记住用户信息选项 打钩,表示 记住用户的登陆信息,并回显到对应 的输入框中, 同时回显打钩的状态是勾选状态。使用checbox 完成勾选按钮。退出登陆的需求分析:进入主界面 点击退出登陆按钮,跳转到logoutServlet中,同时在service方法中销毁session ,然后重定向到登陆界面(login.jsp)
案例流程图:

案例开发步骤:
-
1 . 编写登陆页面(login.jsp)
在页面中提供用户名控件(type类型使用text),密码控件(type类型使用password),记住密码控件(type类型使用checkbox),登陆按钮控件(type类型使用submit)。
-
2 . 编写处理登陆页面的Servlet(LoginServlet)
在service方法中,
- a 首先判断用户在登陆页面上是否勾选记住用户和密码按钮。
如果勾选了,使用Cookie把用户名和密码存入浏览器,使用Session把按钮的状态记录下来。
如果没有勾选,使用Cookie把用户名和密码重置为空字符串,使用Session把按钮的状态设置为null。
-
b 判断用户名和密码是否正确
如果正确,把用户名存入session中,然后重定向到主页面。
如果不正确,重定向到登陆页面。
-
3 . 编写主页面(main.jsp)
编写一段文本,从session中获取用户信息进行显示,增加退出登陆按钮(就是一个a 标签,超链接(定位到logoutServlet))
-
4 . 编写处理退出登陆功能的Servlet(LogoutServlet)
在service方法中,
-
a 首先销毁session。
-
b 重定向到登陆页面。
-
案例代码:
login_synthetical.jsp:
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username" value="${cookie.username.value}"><br/><br/>
密 码:<input type="password" name="pwd" value="${cookie.pwd.value}"><br/><br/>
<input type="checkbox" name="remember" ${empty remember ? "": "checked='checked'"}> 记住密码<br/>
<input type="submit" value="登陆" style="margin-left: 150px">
</form>
</body>
main_synthetical.jsp:
<body>
<table width="50%" align="center">
<tr>
<td>
<div align="right"><a href="/logout">退出登陆</a></div>
</td>
</tr>
<tr>
<td>
<div style="font-size: 60px" align="center"> 欢迎${USER_IN_SESSION} 回家 </div>
</td>
</tr>
</table>
</body>
LoginServlet:
@WebServlet("/login")
public class LoginServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
String remember = req.getParameter("remember");
// 处理是否记住用户名和密码
if (remember==null){
// 不需要记住用户名和密码
// 重置Cookie
Cookie cookieName = new Cookie("username","");
Cookie cookiePwd = new Cookie("pwd","");
// 响应给浏览器进行存储
resp.addCookie(cookieName);
resp.addCookie(cookiePwd);
req.getSession().setAttribute("remember",null);
}else {
// 需要记住用户名和密码
// 设置Cookie
Cookie cookieName = new Cookie("username",username);
Cookie cookiePwd = new Cookie("pwd",pwd);
// 响应给浏览器进行存储
resp.addCookie(cookieName);
resp.addCookie(cookiePwd);
req.getSession().setAttribute("remember",remember);
}
// 处理账号和密码的验证
if ("admin".equals(username) && "1234".equals(pwd)){
// 登陆成功,跳转到主页面
req.getSession().setAttribute("USER_IN_SESSION",username);
resp.sendRedirect("/main.jsp");
}else{
// 登陆失败,跳转到登陆页面
resp.sendRedirect("/login.jsp");
}
}
}
LogoutServlet:
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 销毁session
req.getSession().invalidate();
// 重定向到登陆页面
resp.sendRedirect("/login.jsp");
}
}
最后在总结一下Cookie和Session的区别和联系
Cookie 和 Session的 区别:
1、存放位置不同
Cookie保存在客户端,Session保存在服务端。
2 、存取方式的不同
Cookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二进制数据,需求先进行编码。Cookie中也不能直接存取Java对象。若要存储略微复杂的信息,运用Cookie是比拟艰难的。
而Session中能够存取任何类型的数据,包括而不限于String、Integer、List、Map等。Session中也能够直接保管Java Bean乃至任何Java类,对象等,运用起来十分便当。能够把Session看做是一个Java容器类。
3、安全性(隐私策略)的不同
Cookie存储在浏览器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以至修正Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。 假如选用Cookie,比较好的方法是,敏感的信息如账号密码等尽量不要写到Cookie中。最好是像Google、Baidu那样将Cookie信息加密,提交到服务器后再进行解密,保证Cookie中的信息只要本人能读得懂。而假如选择Session就省事多了,反正是放在服务器上,Session里任何隐私都能够有效的保护。
4、有效期上的不同
只需要设置Cookie的过期时间属性为一个很大很大的数字,Cookie就可以在浏览器保存很长时间。 由于Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过期时间默许为–1,只需关闭了浏览器(一次会话结束),该Session就会失效。
5、对服务器造成的压力不同
Session是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。
***6、 跨域支持上的不同 ***
Cookie支持跨域名访问,例如将domain属性设置为“.baidu.com”,则以“.baidu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中。而Session则不会支持跨域名访问。Session仅在他所在的域名内有效。
7、工作原理的不同
Cookie的工作原理:

Session的工作原理:

Cookie 和 Session的 联系:
session需要借助cookie才能正常工作,如果客户端完全禁止cookie,session将失效,因为session是由应用服务器维持的一个服务端的存储空间,用户在连接服务器时,会由服务器生成唯一的JSESSIONID,用该JSESSIONID为标识来存取服务端的session空间。而sessionid存储在cookie中,用户提交页面时会将这个JSESSIONID提交到服务端,来存取session数据.这一过程是不用开发人员干预的,所以一旦客户端禁用cookie,那么session也会失效;session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE。JSESSIONID是服务器和客户端链接时候随机分配的
网友评论