Cookie和Session详解

http是一个无状态的协议,对于客户端的每次请求服务端在发送完响应后一般会把连接给断开,下次发送请求又会建立一个新的连接,这样就产生了服务端无法判断客户端身份的问题,Cookie和Session就是解决服务端和客户端交互问题的两种方式。
1.Cookie
1).概念
服务端在客户端存放的文件,里面记录了一些客户端相关的信息以便于下次请求的时候再带回服务器去。为什么需要cookie?因为http协议是无状态的,没法确定两次请求是否来于同一客户端,这时候需要客户端在请求中携带一些特定的信息起到身份标识等作用;通过这些信息可以方便确定请求对应的客户端,从而更好的与客户端进行交互。
2).cookie的属性
当前的cookie有两个版本:Version0和Version1,它们有两种设置响应头的标识,分别是”Set-Cookie”和”Set-Cookie2”,这两个版本的属性有些不同,目前Servlet规范中不支持Set-Cookie2响应头,但是在一些属性确是可以设置到Set-Cookie1中的,比如Max-age等。下面两个表详细描述了Cookie的相关属性。
Version 0属性项设置

属性项 属性项介绍
Name=Value 键值对,设置需要保存的key/value,名字不能和其它属性重复
Expires 过期时间(2017-11-12:09:21:23 23形式),在设置的时间点后,该Cookie会失效
Domain 生成该Cookie的域名,如domain=”www.baidu.com”
Path 该Cookie是在当前哪个路径下生成的,如Path=”/”表示在根目录下生成
Secure 如果设置了这个属性,那么只会在SSH连接时才会回传该Cookie

Version 1属性项设置

属性项 属性项介绍
NAME=VALUE 与Version 0相同
Version Set-Cookie响应头的为0,Set-Cookie2响应头对应的1
Comment 注释项,用户说明该Cookie有何用途
CommentURL 服务器为该Cookie提供的URL注释
Discard 是否在会话结束后丢弃该Cookie项
Domain 类似于Vesion 0
Max-age 最大失效时间,与Version 0不同的是,这里设置的是多少秒之后失效
Path 类似于Vesion 0
Port 该Cookie可以在什么端口回传服务端,如果有多个端口,以逗号隔开,如”Port=80,81,8080”
Secure 类似于Version0

除了上面这些属性,其实Cookie还有个HttpOnly属性,如果这个属性为true,则表示该Cookie不在客户端展示出来,仅可以通过服务端进行修改,加强了一些Cookie的安全性。
对于Max-Age属性,需要详细解释下:
如果max-age属性为正数,则表示该cookie会在max-age秒之后自动失效。浏览器会将max-age为正数的cookie持久化,即写到对应的cookie文件中。无论客户关闭了浏览器还是电脑,只要还在max-age秒之前,登录网站时该cookie仍然有效。
如果max-age为负数,则表示该cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该cookie即失效。max-age为负数的Cookie,为临时性cookie,不会被持久化,不会被写到cookie文件中。cookie信息保存在浏览器内存中,因此关闭浏览器该cookie就消失了。cookie默认的max-age值为-1。
‍如果max-age为0,则表示删除该cookie。cookie机制没有提供删除cookie的方法,因此通过设置该cookie即时失效实现删除cookie的效果。失效的Cookie会被浏览器从cookie文件或者内存中删除。
如果不设置expires或者max-age这个cookie默认是Session的,也就是关闭浏览器该cookie就消失了。
上面讲到Version 1版本的Cookie还没有被支持,但是Verson 0 版本的Cookie中又可以引用一些前者的属性,下面通过javax.servlet.http.Cookie的源码来看下具体都有哪些值:

  //
    // The value of the cookie itself.
    //

    private String name;    // NAME= ... "$Name" style is reserved
    private String value;   // value of NAME

    //
    // Attributes encoded in the header's cookie fields.
    //

    private String comment; // ;Comment=VALUE ... describes cookie's use
                // ;Discard ... implied by maxAge < 0
    private String domain;  // ;Domain=VALUE ... domain that sees cookie
    private int maxAge = -1;    // ;Max-Age=VALUE ... cookies auto-expire
    private String path;    // ;Path=VALUE ... URLs that see the cookie
    private boolean secure; // ;Secure ... e.g. use SSL
    private int version = 0;    // ;Version=1 ... means RFC 2109++ style
    private boolean isHttpOnly = false;

3).在Servlet中操作Cookie
通过request.getCookies()方法可以拿到请求带上来的所有Cookie,通过名字可以拿到指定的Cookie。

  Cookie[] cookies = req.getCookies();
        for(Cookie cookie : cookies){
            System.out.println(cookie.getValue());
            if(cookie.getName().equals("name")){
                System.out.println(cookie.getValue());
            }
        }

通过response.addCookie()方法可以增加一个新的Cookie

  Cookie cookie = new Cookie("userName", "test");
        cookie.setMaxAge(1000);
        cookie.setPath("/");
        resp.addCookie(cookie);

Servlet规范中没有显示提供删除一个Cookie的方法,只需要把Cookie的maxAge设为0,就可以令Cookie失效达到删除的目的了。比如说想删除上面新添加的那个Cookie:

   Cookie cookie = new Cookie("userName", "test");
        cookie.setMaxAge(0);
        cookie.setPath("/");
        resp.addCookie(cookie);

这里需要注意的是只有name/value、path、domain都相同的才能算是同一个Cookie,把这样Cookie的maxAge设置为0才能起到删除的作用,如果有一个属性不一致,比如path不一样,则操作的不是同一Cookie。可以通过如下方式来删除所有的Cookie:

 Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            cookie.setMaxAge(0);
            resp.addCookie(cookie);
        }

4).Cookie工作机制
当我们通过Response.addCookie()方法增加了一个Cookie之后,它是如何添加到响应头中带到客户端的。下面以tomcat为例来了解下相关流程。下图是Tomcat如何创建Set-Cookie响应头的。
这里写图片描述
简要的来说就是将Cookie中的各个属性拼成一个字符串,字符串的格式形如:userName=”test”;Version=”1”;domain=”www.baidu.com”;Max-Age=1000然后将这个字符串以Set-Cookie的名称加入MimeHeaders中去。有几点需要注意的地方:

  • 所创建的Cookie的NAME不能和Set-Cookie和Set-Cookie2中属性值名称一样,否则会抛异常。
  • 所创建Cookie的NAME和VALUE不能设置成非ASCII字符,如果要使用中文,可以通过URLEncoder,否则会抛异常
  • 当NAME和VALUE的值出现一些TOKEN字符(如”\”,”,”等),Cookie Version会自动设为1
  • 当在该Cookie的属性项中出现Version为1的属性项时,构建HTTP响应头同样会将Version设置为1

如果我们通过response.addCookie()来添加多个Cookie,那么这多个都会被以Set-Cookie的名称加入到Headers中去,这样就有一个问题,在返回客户端的时候,这些具有相同名称的头部是否会合并成同一个头部返回?从返回代码中来看,并不会出现这种情况。而是各个Header顺序写入的,客户端也是顺序解析这些header的,也就是说可能需要解析多个名称为Set-Cookie的头部。
上面讲的是服务端如何创建并传输Cookie的,对于客户端,当我们请求某个URL路径时,浏览器会根据这个路径取到符合条件的Cookie放在请求头中传输给服务端。 值得注意的虽然在Http中Cookie仅仅是一个头部,没有加上什么限制,但因为最终的存储还是在客户端中,所以大部分的客户端还是会限制Cookie的数量和大小,比如Chrome就限制每个域名最多50个Cookie,而FireFox不仅限制每个域名最多50个cookie,并且还限制了总
长度不超过4097字节。

2.Session
1).概述
前面介绍了Cookie可以让服务端程序跟踪客户端的每个访问,但每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无疑增加了客户端和服务端之间的数据传输量,另外因为Cookie是暴露给客户端的,客户端可以对其进行修改,所以还存在一定安全性问题。Session的出现正是为了解决这个问题。
同一个客户端每次和服务端交互时,不需要每次都传回所有的Cookie值,而只需要传回一个ID,这个ID是客户端第一次范围服务器时候生成的,而且每个客户端都是唯一的,这样每个客户端都有个唯一的ID,每次访问的时候只需要回传这个ID,然后通过这个ID可以在服务端找到包含客户端信息的文件,也就可以记录客户端的访问信息了。
客户端访问tomcat服务器HttpServletRequest的getSession(true)的时候创建,tomcat的ManagerBase类提供创建sessionid的方法:随机数+时间+jvmid;创建之后就会填充到Cookie中去。
2).工作模式
Session是基于Cookie来工作的,总的来说一共有三种模式可以使得客户端的SessionId传递到服务端。
1) 基于URL Path Parameter 默认支持
2) 基于Cookie,如果没有修改Context容器的cookie标识,那么也是默认支持的
3) 至于SSL,默认不支持,只有connector.getAttribute(“SSL Enable”)为true时才支持
第一种情况下,当浏览器不支持Cookie功能时,浏览器会将用户的SessionCookieName重写到用户请求的URL参数中,它的传递格式如/path/servlet;name=value1;name=value2?name=value3,这里servlet后面的就是path Parameter,服务器会从这个path Parameter中取到用户配置的SessionCookieName,这里的SessionCookieName默认就是JSESSIONID,如果在web.xml中配置了session-config,那么其下的cookie-config下的name属性就是这个SessionCookieName的值。 如果客户端也支持Cookie,那么服务端仍然会解析Cookie中的SessionId值,并覆盖url中的.如果是第三种情况,那么服务端会根据java.servlet.request.ssl_session属性值来设置sessionId。
下面以Tomcat为例来具体说明下Session的管理流程(每种Servlet容器管理Session的方式都是自定义的)。首先StandarManger类负责Servlet容器中所有的Session对象生命周期的管理。当Servlet容器关闭或重启时,StandarManger会调用unload方法将所有未过期的Session对象持久化到一个以”SESSION.ser”为文件名的文件中,到Servlet重启时,也就是StandarManger初始化时,它会重新读取这个文件,按照下面的状态图重新恢复。
这里写图片描述
另外StandarManger的sessions集合中的StandarManger对象并不是永久保存的,否则Servlet容器的内存将会被很快耗光,所以必须给每个Session定义一个有效时间,超过这个时间则Session对象将会被清除.在Tomcat中这个有效时间是60s(由maxInactiveInterval属性控制),超过60s该Session将会过期.检查每个Session的有效性是在Tomcat一个后台线程中完成的,过期Session的状态如图所示。
这里写图片描述
除了后台线程检查Session是否过期之外,当调用request.getSession()方法时也会检查Session是否过期.值得注意的是,request.getSession()方法永远都会返回一个正在生效的Session,如果上次改SessionId对应的Session已经失效了,那么就会重新生成一个新Session对象,这时候可能出现在原先Session中设置的Attribute已失效的情况;如果只想看下客户端关联的Session对象是否已存在,但就算不存在或过期也不想重建一个新的,可以通过request.getSession(false)方法来查看,如果没有关联的Session对象,则会返回null。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章