详解 cookie/session

会话(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 的区别

  1. 存储位置不同:cookie数据存放在客户的浏览器上,session数据放在服务器上

  2. 安全程度不同:cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session

  3. 性能使用程度不同:

    session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。

  4. 数据存储大小不同:

    单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制。

  5. 会话机制不同:

    session会话机制:session会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息。

    cookies会话机制:cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web服务器使用HTTP标头将cookie发送到客户端。在客户端终端,浏览器解析cookie并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些cookie。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值