会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份
Cookie
什么是 cookie?
储存在用户本地终端的数据,类型为“小型文本文件”,是某些网站为了辨别用户身份,进行会话跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
cookie 机制
Cookie技术是客户端的解决方案,Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。也就是,当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,而是存放于HTTP响应头(Response Header);当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置;自此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头(Request Header)了。有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。
因为Web 应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。而 cookie 可以弥补HTTP协议无状态的不足,在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。
其实本质上cookies就是http的一个扩展。有两个http头部是专门负责设置以及发送cookie的,它们分别是Set-Cookie以及Cookie。当服务器返回给客户端一个http响应信息时,其中如果包含Set-Cookie这个头部时,意思就是指示客户端建立一个cookie,并且在后续的http请求中自动发送这个cookie到服务器端,直到这个cookie过期。如果cookie的生存时间是整个会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会自动清除这个cookie。另外一种情况就是保存在客户端的硬盘中,浏览器关闭的话,该cookie也不会被清除,下次打开浏览器访问对应网站时,这个cookie就会自动再次发送到服务器端。一个cookie的设置以及发送过程分为以下四步:
客户端发送一个请求到服务器 -----> 服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部 -----> 客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部 -----> 服务器返回响应的数据
在客户端的第二次请求中包含的Cookie头部中,提供给了服务器端可以用来唯一标识客户端身份的信息。这时,服务器端也就可以判断客户端是否启用了cookies。当然,用户可能在和应用程序交互的过程中突然禁用cookies的使用。
代码测试:
在doGet方法中,new了一个Cookie对象并将其加入到了HttpResponse对象中
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("name","cookie test");
// 设置生命周期为MAX_VALUE
cookie.setMaxAge(Integer.MAX_VALUE);
resp.addCookie(cookie);
}
可见Response Headers中包含Set-Cookie头部,而Request Headers中包含了Cookie头部
cookie 属性
1、name-value
键值对,设置Cookie的名称及相对应的值,对于认证Cookie,Value值包括Web服务器所提供的访问令牌,注意这里的 NAME 不能和其他属性项的名字一样
Cookie cookie = new Cookie("name","cookie test");
2、Expires属性
设置Cookie的生存期。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话性Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效。
Cookie 中的 maxAge 用来表示该属性,单位为秒。Cookie 中通过 getMaxAge() 和 setMaxAge(int maxAge) 来读写该属性。maxAge有3种值,分别为正数,负数和0。
如果 maxAge 属性为正数,则表示该 Cookie 会在 maxAge 秒之后自动失效。浏览器会将 maxAge 为正数的 Cookie 持久化,即写到对应的 Cookie 文件中(每个浏览器存储的位置不一致)。无论客户关闭了浏览器还是电脑,只要还在 maxAge 秒之前,登录网站时该 Cookie仍然有效。
// 设置生命周期为MAX_VALUE,永久有效
cookie.setMaxAge(Integer.MAX_VALUE);
当maxAge属性为负数,则表示该Cookie只是一个临时Cookie,不会被持久化,仅在本浏览器窗口或者本窗口打开的子窗口中有效,关闭浏览器后该Cookie立即失效。
// MaxAge为负数,是一个临时Cookie,不会持久化
cookie.setMaxAge(-1);
当maxAge为0时,表示立即删除Cookie
// 删除一个cookie
cookie.setMaxAge(0);
设置负值和0的区别
-
maxAge设置为0表示立即删除该Cookie
-
maxAge设置为负数,Cookie仍然会存在一段时间直到关闭浏览器或者重新打开浏览器。
3、Path 属性
定义了Web站点上可以访问该Cookie的目录
例如,如果只允许/sessionWeb/下的程序使用Cookie,可以这么写
//设置 Cookie 是在当前的哪个路径下生成的
cookie.setPath("/sessionWeb/");
设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾
注意:页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。
4、Domain 属性
指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围
5、Secure 属性(安全属性)
指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到Pharming攻击所伪造的网站
如果使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie
cookie.setSecure(true)
提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密
6、HTTPOnly 属性
用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头
cookie 修改、删除
HttpServletResponse提供的Cookie操作只有一个addCookie(Cookie cookie),所以想要修改Cookie只能使用一个同名的Cookie来覆盖原先的Cookie。如果要删除某个Cookie,则只需要新建一个同名的Cookie,并将maxAge设置为0,并覆盖原来的Cookie即可。
新建的Cookie,除了value、maxAge之外的属性,比如name、path、domain都必须与原来的一致才能达到修改或者删除的效果。否则,浏览器将视为两个不同的Cookie不予覆盖。
值得注意的是,从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name和value属性,maxAge属性只被浏览器用来判断Cookie是否过期,而不能用服务端来判断。
我们无法在服务端通过cookie.getMaxAge()来判断该cookie是否过期,maxAge只是一个只读属性,值永远为-1。当cookie过期时,浏览器在与后台交互时会自动筛选过期cookie,过期了的cookie就不会被携带了。
cookie 的域名
Cookie是不可以跨域名的,域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去,这是由Cookie的隐私安全机制决定的,隐私安全机制禁止网站非法获取其他网站的Cookie。
正常情况下,同一个一级域名下的两个二级域名也不能交互使用Cookie,比如 test1.demo.com 和 test2.demo.com,因为二者的域名不完全相同。如果想要demo.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为 .demo.com
,这样使用test1.demo.com 和 test2.demo.com就能访问同一个cookie
cookie.setDomain(".demo.com");
注意:domain参数必须以点(".")开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端
补充:
一级域名又称为顶级域名,一般由字符串+后缀组成。熟悉的一级域名有baidu.com,qq.com。com,cn,net等均是常见的后缀。
二级域名是在一级域名下衍生的,比如有个一级域名为 demo.com,则 blog.demo.com和 www.demo.com 均是其衍生出来的二级域名。
Session
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力
什么是Session?
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上(内存或硬盘)。一般Session存储在服务器的内存中,tomcat 的 StandardManager 类将 session 存储在内存中,也可以持久化到 file,数据库,memcache,redis等。
客户端只保存 sessionid 到 cookie 中,而不会保存 session,session 销毁只能通过 invalidate 或超时(默认30分钟),关掉浏览器并不会关闭session。
客户端浏览器访问服务器的时候,服务器把客户端信息以类似于散列表的形式记录在服务器上,这就是 Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。 如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。
Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。 当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否包含一个session标识(即,sessionId)。如果已经包含一个sessionId说明以前已经为此客户端创建过session,服务器就按照sessionId把这个session检索出来使用。 如果客户请求不包含sessionId,则为此客户创建一个session并且生成一个与此session相关的sessionId,这个sessionId将在本次响应中返回给客户端保存。
Session 读取
Session对应的类为javax.servlet.http.HttpSession类。 每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。 Session对象是在客户端第一次请求服务器的时候创建的。 Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。 Servlet里通过request.getSession()方法获取该客户的Session。
HttpSession session = request.getSession();
//HttpSession session = request.getSession(true);//先创建再返回
//HttpSession session = request.getSession(false);//返回NULL
//设置属性
session.setAttribute("name","sessionDemo");
//读取属性
session.getAttribute("name");
//移除属性
session.removeAttribute("name");
//设置有效期,单位为秒,-1代表永不过期
session.setMaxInactiveInterval(1000);
//使其失效(注销Session)
session.invalidate();
Servlet中必须使用request来编程式获取HttpSession对象,而JSP中内置了Session隐藏对象,可以直接使用。如果使用声明了<%@ page session="false" %>
,则Session隐藏对象不可用,使用将会抛出异常。
当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。 Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。 isNew()
是指是否一个新创的session,当用户访问一个支持session的jsp网页时,session被创建,尽管有时session里面并没有任何东西。
Session的生命周期
Session在用户第一次访问服务器的时候自动创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。
Session是服务器建立的,但是服务器不会主动建立Session,而是要通过程序通知服务器时request.getSession()
,才会建立一个会话。在jsp文件中有一个默认的属性 <%@ page session=“true” %>
,翻译成java后就是:session = pageContext.getSession();
所以每当我们访问一个 jsp 文件时,如果没有修改默认的属性值时,就会默认建立一个会话,这也是大多数人认为自动创建Session的原因。所以,Tomcat这类服务器不会自动的创建session,只有当我们主动通知它时,才会创建会话。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
//建立会话
HttpSession session = req.getSession();
session.setAttribute("name","sessionDemo");
//获取sessionId
String id = session.getId();
if (session.isNew()){
resp.getWriter().write("session创建成功,ID:" + id);
}else{
resp.getWriter().write("session以及在服务器中存在了,ID:" + id);
}
}
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。随着越来越多用户登录,Session所需要的服务器内存量也会不断增加。访问Web应用程序的每个用户都生成一个单独的Session对象。每个Session对象的持续时间是用户访问的时间加上不活动的时间。如果每个Session中保持许多对象,并且许多用户同时使用Web应用程序(创建许多Session),则用于 Session持久性的服务器内存量可能会很大,从而影响了可伸缩性。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
maxInactiveInterval
属性
//默认 time 1800
int time = session.getMaxInactiveInterval();
session.setMaxInactiveInterval(Integer.MAX_VALUE);
默认有效期为30分钟,30分钟内没有"活跃"则失效。如果"活跃"则重新计算生命周期。
Session的超时时间也可以在web.xml中修改
<!--设置Session默认的失效时间-->
<session-config>
<!--15分钟后Session自动失效,以分钟为单位-->
<session-timeout>15</session-timeout>
</session-config>
虽然 Session 保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。 因为 Session 需要使用 Cookie 作为识别标志。HTTP 协议是无状态的,Session 不能依据 HTTP 连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID 的 Cookie,它的值为该 Session 的 id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且不同的浏览器的窗口间不共享(同一浏览器共享),关闭浏览器就会失效。
当打开多个浏览器时:
因此同一机器的不同类型的浏览器的窗口访问服务器时,会生成不同的Session。
但是由浏览器窗口内的链接、脚本等打开的新窗口除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。
URL重写
如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?例如,绝大多数的手机浏览器都不支持Cookie。
Java Web提供了另一种解决方案:URL地址重写。
//<a href="<%=response.encodeUrl(url)%>"></a>
resp.encodeURL("");
encodeURL的两个作用:
1、转码
转中文的编码,或者一些其他特殊的编码。就好比如网页的链接中存在中文字符,就会转换成为一些百分号或者其他的符号代替。
2、当浏览器不支持cookie或将Cookie功能禁用的时候,可以使用encodeURL()方法
URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。 这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。 encodeurl()方法在使用时,会首先判断Session是否启用,如果未启用,直接返回url。 然后判断客户端是否启用Cookie,如果未启用,则将参数url中加入SessionID信息,然后返回修改的URL;如果启用,直接返回参数url。
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
req.getSession();
// 实现URL重写,这个重写很常见,所以SUN公司定义了一个方法,超链接后面会跟上session的id号,上面的代码已经获取session了
String url1 = resp.encodeURL("/A");
String url2 = resp.encodeURL("/B");
System.out.println(url1);
System.out.println(url2);
out.print("<a href='" + url1 +"'>A</a></br>");
out.print("<a href='" + url2 +"'>B</a>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
String sessionId = session.getId();
Cookie cookie = new Cookie("JSESSIONID", sessionId);
//设置为“/”时允许所有路径使用Cookie
cookie.setPath("/");
//设置生命周期
cookie.setMaxAge(60 * 30);
resp.addCookie(cookie);
session.setAttribute("name","A-Servlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
String name = (String) session.getAttribute("name");
out.print(name);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
禁用 cookie的情况下运行效果:
控制台输出结果:
没有禁用cookie的情况下:
控制输出结果:
SessionId
sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionid和它对应。tomcat生成的sessionid叫做jsessionid。
session在访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid
。
其存储在服务器的内存中,tomcat的StandardManager类将session存储在内存中,也可以持久化到file,数据库,memcache,redis等。客户端只保存sessionid到cookie中,而不会保存session,session销毁只能通过invalidate或超时,关掉浏览器并不会关闭session。
session不会因为浏览器的关闭而删除。但是存有session ID的cookie的默认过期时间是会话级别。也就是用户关闭了浏览器,那么存储在客户端的session ID便会丢失,但是存储在服务器端的session数据并不会被立即删除。从客户端即浏览器看来,好像session被删除了一样(因为我们丢失了session ID,找不到原来的session数据了)。
Cookie 与 Session 的区别
-
存储位置不同:cookie数据存放在客户的浏览器上,session数据放在服务器上
-
安全程度不同:cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session
-
性能使用程度不同:
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
-
数据存储大小不同:
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制。
-
会话机制不同:
session会话机制:session会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息。
cookies会话机制:cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web服务器使用HTTP标头将cookie发送到客户端。在客户端终端,浏览器解析cookie并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些cookie。