会话代理中的 cookie 和 session

2,366 阅读11分钟
原文链接: xdmonkey.me

  会话跟踪的英文是Session,但是这种Session和我们下面写的会话跟踪的两种技术分支中的Session是不同的。

《Java Web整合开发王者归来》P134:在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于一个会话的,不能放入用户B或用户C的购物车,这不属于一个会话。
  而Web应用程序是使用HTTP协议传输数据的,HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,比如引用一种机制。

  这种机制就是会话跟踪,我们常用的会话跟踪机制有四种:隐藏表单域、URL重写、cookie以及session,这里仅详细谈一下cookie和session。


《Java Web整合开发王者归来》P134:由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从服务器上确认客户身份了。这就是Cookie的工作原理。

  Cookie是浏览器提供的一种技术,本身就是为了为了让一些保存在客户端就可以或者在客户端进行处理就可以的数据放在本身使用的计算机中,而不需要经过网络的传输,从而减少服务端的负载,同时提高网页处理的效率,但是同时也因为Cookie是服务端保存在客户端的信息,因此其安全性也是非常差的。
  在自己的Web开发过程中,发现自己只要写了一个表单,并且成功让页面实现了跳转(实际上自己写了一个页面A,其在登陆成功之后会跳转到成功页面B,但是跳转到页面B之后,按返回键返回A页面(这里因为已经使用了session和重定向,因此我们如果直接输入页面A的URL,会重定向到B页面,因此只能通过使用返回的手段来得到A对应的页面,这时候我们点击A页面的登陆按钮,这时候因为会先验证session,因此即使我们输入了错误的密码,也会因为session存在而实现跳转,但是密码是错误的却也提示我们是否保存cookie)),Chrome就会问使用者是否保存cookie,自己的项目是用maven构建的Spring MVC项目。
  在JSP/Servlet中,专门提供了javax.servlet.http.Cookie操作类,类中的定义方法有:

public Cookie(String name,String value);//构造函数,实例化Cookie对象,同时设置名称和内容
public String getName();//取得Cookie的名称
public String getValue();//取得Cookie的内容
public void setMaxAge();//设置Cookie的保存时间,以秒为单位

  Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时可以使用多个Cookie。

String username="";
Cookie[] cookies=request.getCookies();
for(int i=0;cookies!=null&&i

  所有的Cookie是由服务端设置到客户端上去的(之前的项目遇到的这种现象可能是浏览器行为?),所以要向客户端添加Cookie,必须使用response对象的以下方法:

public void addCookie(Cookie cookie);//向客户端设置Cookie

  举例:

//定义新的Cookie对象,下面的举例是在前端jsp中完成的
//实际开发中我们也可以在JS中完成Cookie的设置,在JSP技术被逐渐摈弃的今天,
//在JS中或者在后台设置Cookie应该是更加常见的做法(我感觉的,不一定准确)。
//Cookie c1=new Cookie("lxh","LiXingHua");
//向客户端增加Cookie
response.addCookies(c1);

  我们后续可以在JSP中取Cookie:

<%
    Cookie c[]=request.getCookies();
//这里的循环用于取出Cookie中所有的key-value
    for(int x=0;x
    

<%=c[x].getName()%>--><%=c[x].getValue()%>

<% } %>

  最后我们可以发现屏幕上输出的有两行数据,一行是我们之前设置好的lxh–>LiXingHua,另外一行是 JSESSIONID。关于JSESSIONID,在后面会讲到。因为这个JSESSIONID的原因,除开浏览器自己的设置Cookie行为,如果我们用request.getCookies();取出的Cookies数组的长度也至少为1,里面存储的就是JSESSIONID。
  在除开浏览器自己的Cookie行为之外,如果是程序员自己设定的Cookie,我们发现一个现象,当我们重启浏览器之后,之前我们在代码里设置的全部Cookie就都不存在了。

《Java Web开发实战经典》P155:实际上,我们之前设置的Cookie并没有真正保存在客户端上,而是保存在客户端浏览器上,所以,当浏览器重新启动后,之前所设置的全部Cookie就不在了,则以后使用getCookies()方法时取得的就是null,那么在操作时就出现了NullPointerException异常。此时,如果想要真正将Cookie保存在客户端上,就必须设置Cookie的保存时间,使用setMaxAge()方法即可。

Cookie的有效期

  Cookie的maxAge决定着Cookie的有效期,单位为秒,Cookie中通过getMaxAge()方法和setMaxAge(int maxAge)方法来读写maxAge属性。
  如果maxAge属性为正数,则表示该Cookie会在maxAge秒后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登陆网站时该Cookie仍然有效。如果我们把maxAge设置为Integar.MAX_VALUE,这时候Cookie信息就将永远有效。
  如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中,此时Cookie信息保存在浏览器内存中,因此关闭浏览器该Cookie消失了。Cookie默认的maxAge值为-1。
  如果maxAge为0,则表示删除该Cookie。Cookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会从浏览器从Cookie文件或者内存中删除。
  很遗憾的是,response对象提供的Cookie操作方法只要一个添加操作add(Cookie cookie)。要想修改Cookie只能使用一个同名的Cookie来覆盖原来的Cookie,而删除一个Cookie对象也只能通过将maxAge设置为0才可以。

Cookie的不可跨域名性

  很多网站都会使用Cookie,但是不同网站之间的Cookie之间是否可以相互访问的操作呢?
  答案是否定的:

《Java Web整合开发王者归来》P138:Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie.
  Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google和Baidu的域名不一样,因此Google不能操作Baidu的Cookie。
  需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。
  注意:用户登录www.google.com之后会发现访问image.google.com时登录信息仍然有效,而普通的Cookie是做不到的,这是因为Google做了特殊处理。
《Java Web整合开发王者归来》P146:正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相等。如果想要想所有helloweenvsfei.com名下二级域名都可以使用该Cookie,需要设置Cookie的domain参数,例如:

Cookie cookie=new Cookie("time","20080808");//新建Cookie
cookie.setDomain(".helloweenvsfei.com");//设置域名

  最后,虽然Cookie中可以保存信息,但是并不能无限制地保存,一般一个客户端最多只能保存300个Cookie,所以数据量太大的时候将无法使用Cookie。

Session


《Java Web整合开发王者归来》P152:Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问的时候只需从该Session中查找该客户的状态就可以了。
  如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候需要查询客户档案表就可以了。

  Session对象是javax.servlet.http.HttpSession接口的实例化对象,所以session只能应用在HTTP协议中。HttpSession接口的常用方法如下所示:

public String getId();//取得Session Id,至于Session Id,在后面会讲到
public long getCreationTime();//取得session的创建时间
public long getLastAccessedTime();//取得session的最后一次操作时间
public boolean isNew();//判断是否是新的session(新用户)
public void invalidate();//让session失效
public Enumeration getAttributeNames();//得到全部属性的名称,用枚举的形式

  当然还有setAttribute和getAttribute方法用来设置和取出对应key的value。
  首先Session是依赖于Cookie的,这句话解释起来相对而言比较费劲,这句话其实不太准确,准确的表达式:当Session Id保存在Cookie里的时候是这样的。

Session Id

  我们之前看到Session中有一个getId()方法,同时我们还说Session一般情况下是依赖于Cookie,实际上这两者是关联的,这个getId()方法得到的返回值得到的Session Id就是体现Session依赖于Cookie的地方。我们在之前介绍Cookie的时候输出所有的key-value的时候,发现输出了JSESSIONID,实际上JSESSIONID只是tomcat里面对Session Id的Key的命名罢了,对于这个ID的真身,一般我们就叫他Session Id。换种方式来理解Session Id:
  我们从服务端来看,对于一个域名下的网站,对于每个用户,我们都保存了的Session,但是我们怎么知道一个Session是属于哪个用户的呢?虽然我们在服务端隐藏了这部分的实现,但是这不代表不存在。
  实际上:服务器在为不同用户生成Session的时候,为了以后再能认出这个用户,就为每个用户生成一个Session Id,并将这个Sessio Id发送给不同的用户浏览器,在浏览器那里用一个Cookie对象,假如有A或者B两个不同用户的浏览器,以后它们在请求的时候就会在请求头Cookie中携带这两个参数。这样的话,服务器在收到http请求后只要读取http请求头的Cookie部分,发现了Session Id的值就可以发现这是谁的请求。
  在java、tomcat环境下我们搭建服务器,对于同一个浏览器用户,Cookie中的JSESSIONID存储的内容和Session里的getId()函数的返回值实际上是一个东西,都是SessionId。
  实际上,Session Id的保存方式也不只是通过Cookie这一种方式,还可以通过重写URL、表单隐藏域这些方式,只不过用Cookie来保存Session Id是最常见的一种做法。关于会话管理,可以适当看一下《Servlet和JSP学习指南》会话管理一章(书讲的并不全面,书上的名字是Session管理,实际上名字应该叫会话管理)
  当然,上面提到服务器为不同的用户生成Session,实际上,Session的创建工作是在用到了形如request.getSession()这样的语句的时候。
  request.getSession()是等同于request.getSession(true)的。而对于request.getSession(boolen isNew)而言,尽量使用除非我们确认session存在或者sesson不存在时明确有创建session的需要,否则请尽量使用request.getSession(false)(换句话说如果我们在Session不存在的时候如果不想创建一个新的Sessio就使用false)。
  在使用Session时也必须注意一点,对于每一个已连接上服务器上的用户,如果重新启动服务器,则这些用户再次发出请求实际上表示的都是一个新连接的用户。