Session与Cookie的做用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优势,也有各自的缺陷,然而具备讽刺意味的是它们的优势和它们的使用场景又是矛盾的。例 如,使用Cookie来传递信息时,随着Cookie个数的增多和访问量的增长,它占用的网络带宽也很大,试想假如Cookie占用200个字节,若是一天的PV有几亿,它要占用多少带宽?因此有大访问量的时候但愿用Session,可是Session的致命弱点是不容易在多台服务器之间共享,因此这也限制了Session的使用。java
Cookie的做用我想你们都知道,通俗地说就是当一个用户经过HTTP协议访问一个服务器的时候,这个服务器会将一些Key/Value键值对返回给客户端浏览器,并给这些数据加上一些限制条件,在条件符合时这个用户下次访问这个服务器的时候,数据又将被完整地带回给服务器。web
这个做用就像您去超市购物时,第一次给您办张购物卡,这个购物卡里存放了一些您的我的信息,下次您再来这个连锁超市时,超市会识别您的购物卡,下次直接购物就行了。算法
当初W3C在设计Cookie时实际上考虑的是为了记录用户在一段时间内访问Web应用的行为路径。因为HTTP协议是一种无状态协议,当用户的一次访问请求结束后,后端服务器就没法知道下一次来访问的仍是不是上次访问的用户,在设计应用程序时,咱们很容易想到两次访问是同一人访问与不一样的两我的访问对程序设计和性能来讲有很大的不一样。例如,在一个很短的时间内,若是与用户相关的数据被频繁访问,能够针对这个数据作 缓存,这样能够大大提升数据的访问性能。Cookie的做用正是在此,因为是同一个客户端发出的请求,每次发出的请求都会带有第一次访问时服务端设置的信息,这样服务端就能够根据Cookie值来划分访问的用户了。apache
当前Cookie有两个版本:Version 0和Version 1。经过它们有两种设置响应头的标识,分别是 “Set-Cookie”和“Set-Cookie2”。这两个版本的属性项有些不一样,表 10-1 和表 10-2 是两个版本的属性介绍。后端
表 10-1.Version 0 属性项介绍跨域
属性项 | 属性项介绍 |
---|---|
NAME=VALUE | 键值对,能够设置要保存的Key/Value,注意这里的NAME不能和其余属性项的名字同样 |
Expires | 过时时间,在设置的某个时间点后该Cookie就会失效,如expires=Wednesday,09-Nov-99 23:12:40 GMT |
Domain | 生成该Cookie的域名,如domain="xulingbo.net" |
Path | 该Cookie是在当前的哪一个路径下生成的,如path=/wp-admin/ |
Secure | 若是设置了这个属性,那么只会在 SSH 链接时才会回传该 Cookie |
表 10-2.Version 1 属性项介绍浏览器
属 性 项 | 属性项介绍 |
---|---|
NAME=VALUE | 与 Version 0 相同 |
Version | 经过 Set-Cookie2 设置的响应头建立必须符合 RFC2965 规范,若是经过 Set-Cookie 响应头设置,默认值为 0,若是要设置为 1,则该 Cookie 要遵循 RFC 2109 规范 |
Comment | 注释项,用户说明该 Cookie 有何用途 |
CommentURL | 服务器为此 Cookie 提供的 URI 注释 |
Discard | 是否在会话结束后丢弃该 Cookie 项,默认为 fasle |
Domain | 相似于 Version 0 |
Max-Age | 最大失效时间,与 Version 0 不一样的是这里设置的是在多少秒后失效 |
Path | 相似于 Version 0 |
Port | 该 Cookie 在什么端口下能够回传服务端,若是有多个端口,以逗号隔开,如 Port="80,81,8080" |
Secure | 相似于 Version 0 |
以上两个版本的Cookie中设置的Header头的标识符是不一样的,咱们经常使用的是 Set-Cookie:userName=“junshan”; Domain=“xulingbo.net”,这是 Version 0 的形式。针对 Set-Cookie2 是这样设置的:Set-Cookie2:userName=“junshan”; Domain=“xulingbo.net”; Max-Age=1000。可是在 Java Web 的 Servlet 规范中并不支持 Set-Cookie2 响应头,在实际应用中 Set-Cookie2 的一些属性项却能够设置在 Set-Cookie 中,如这样设置:Set-Cookie:userName=“junshan”; Version=“1”;Domain=“xulingbo.net”;Max-Age=1000。缓存
当咱们用以下方式建立 Cookie 时:tomcat
String getCookie(Cookie[] cookies, String key) { if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(key)) { return cookie.getValue(); } } } return null; } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Cookie[] cookies = request.getCookies(); String userName = getCookie(cookies, "userName"); String userAge = getCookie(cookies, "userAge"); if (userName == null) { response.addCookie(new Cookie("userName", "junshan")); } if (userAge == null) { response.addCookie(new Cookie("userAge", "28")); } response.getHeaders("Set-Cookie"); }
Cookie是如何加到HTTP的 Header 中的?当咱们用 Servlet 3.0 规范来建立一个 Cookie 对象时,该 Cookie 既支持Version 0又支持Version 1,若是您设置了Version 1中的配置项,即便您没有设置版本号,Tomcat在最后构建HTTP响应头时也会自动将Version的版本设置为1。下面看一下 Tomcat是如何调用 addCookie 方法,图 10-1 是 Tomcat 建立 Set-Cookie 响应头的时序图。安全
图10-1.Tomcat建立Set-Cookie响应头的时序图
从图10-1中能够看出,真正构建Cookie是在org.apache.catalina.connector.Response类中完成的,调用generateCookieString方法将Cookie对象构形成一个字符串,构造的字符串的格式如userName=“junshan”;Version=“1”;Domain=“xulingbo.net”; Max-Age=1000。而后将这个字符串命名为Set-Cookie添加到MimeHeaders中。
在这里有几点须要注意:
建立的Cookie的NAME不能和Set-Cookie或者Set-Cookie2的属性项值同样,若是同样会抛IllegalArgumentException异常。
建立Cookie的NAME和VALUE的值不能设置成非ASSIC字符,若是要使用中文,能够经过URLEncoder将其编码,不然将会抛IllegalArgumentException异常。
当NAME和VALUE的值出现一些 TOKEN 字符(如“\”、“,”等)时,构建返回头会将该 Cookie 的 Version 自动设置为 1。
当该 Cookie 的属性项中出现 Version 为 1 的属性项时,构建HTTP消息响应头一样会将Version设置为 1。
不知道您有没有注意一个问题,就是当咱们经过response.addCookie 建立多个Cookie时,这些Cookie最终是在一个Header项中仍是以独立的Header存在的,通俗地说也就是咱们每次建立Cookie时是否都是建立一个以NAME为Set-Cookie的MimeHeaders?答案是确定的。从上面的时序图中能够看出每次调用addCookie的时候,最终都会建立一个Header,可是咱们还不知道最终在请求返回时构造HTTP消息响应头是否将相同Header标识的Set-Cookie值进行合并。
咱们找到Tomcat最终构造HTTP消息响应头的代码,这段代码位于org.apache.coyote.http11. Http11Processor类的prepareResponse方法中,以下所示:
int size = headers.size(); for (int i = 0; i < size; i++) { outputBuffer.sendHeader(headers.getName(i), headers.getValue(i)); }
这段代码清楚地表示,在构建HTTP消息返回字节流时是将 Header 中全部的项顺序地写出,而没有进行任何修改。因此能够想象浏览器在接收HTTP协议返回的数据时是分别解析每个Header项的。
另外,目前不少工具均可以观察甚至能够修改浏览器中的Cookie数据。例如,在Firefox中能够经过HttpFox插件来查看返回的Cookie数据,如图10-2所示。
图 10-2.HttpFox插件展现的Header数据
在cookie项中能够详细查看Cookie属性项,如图 10-3 所示。
图 10-3.HttpFox插件展现的Cookie数据
前面主要介绍了服务端如何建立Cookie,下面看一下如何从客户端获取Cookie。
当咱们请求某个URL路径时,浏览器会根据这个URL路径将符合条件的Cookie放在Request请求头中传回给服务端,在服务器端,服务器(多是tomcat服务器)经过request.getCookies()来取得全部Cookie。
Cookie是HTTP协议头中的一个字段,虽然HTTP协议自己对这个字段并无多少限制,可是Cookie最终仍是存储在浏览器里,因此不一样的浏览器对Cookie的存储都有一些限制,表10-3是一些一般的浏览器对Cookie的大小和数量的限制。
表 10-3.浏览器对Cookie的大小和数量的限制
浏览器版本 | Cookie 数限制 | Cookie 总大小限制 |
---|---|---|
IE6 | 20 个 / 每一个域名 | 4095 个字节 |
IE7 | 50 个 / 每一个域名 | 4095 个字节 |
续表
浏览器版本 | Cookie 数限制 | Cookie 总大小限制 |
---|---|---|
IE8 | 50 个 / 每一个域名 | 4095 个字节 |
IE9 | 50 个 / 每一个域名 | 4095 个字节 |
Chrome | 50 个 / 每一个域名 | 大于 80000 |
FireFox | 50 个 / 每一个域名 | 4097 个字 |
①Session是存储在服务器端的,并且是存储在服务器的内存当中的!(不像Cookie是存储在文件中,因此Session没法查看。)
②一个用户浏览器,独享一个Session域对象!
③session中的属性的默认生命周期是30min,你能够经过web.xml来修改。这样修改:一个地方是tomcat/conf/web.xml。若是发生冲突,则以本身的Web应用下为标准。
④session中能够存放多个属性。
⑤session能够存放对象。
⑥若是session.setAttribute("name", val),若是名字重复,则会替换该属性。
Session不是特别好理解,你能够把Session看做是一个容器,相似HashMap,有两列。每一列就是session的一个属性。
Session之因此能用,就是由于Cookie在传JSESSIONID!因此说Cookie和Session有千丝万缕的联系。
每一个属性包含有两个部分,一个是该属性的名字(String型数据),另一个是它的值(Object型数据)。
Session周期是发呆时间,若是咱们设置Session是10s,是指,在10s内,没有访问过session,session中属性失效,若是在9s的时候,你访问Session,则从新计时。①若是重启tomcatt,或者reload web应用,或者关机了,Session会失效。②咱们也能够经过函数,让session失效,invalidate()==》该方法一般用于安全退出。③若是你但愿某个Session属性失效,可使用方法removeAttribute(java.lang.String name)。
前面已经介绍了Cookie可让服务器端程序跟踪每一个客户端的访问,可是每次客户端的访问都必须传回这些Cookie,若是Cookie不少,这无形地增长了客户端与服务器端的数据传输量,而 Session的出现正是为了解决这个问题。(对比cookie的生命周期,指的是累计时间,无论用户是否访问过该cookie。)
同一个客户端每次和服务端交互时,不须要每次都传回全部的Cookie值,而是只要传回一个ID,这个ID是客户端第一次访问服务器的时候生成的,并且每一个客户端是惟一的。这样每一个客户端就有了一个惟一的ID,客户端只要传回这个ID就好了,这个ID一般是NANE为JSESIONID 的一个Cookie。
下面详细讲一下Session如何基于Cookie来工做。实际上有三种方式能可让 Session 正常工做:
基于URL Path Parameter,默认支持。
基于Cookie,若是没有修改Context容器的cookies标识,默认也是支持的。
基于SSL,默认不支持,只有connector.getAttribute("SSLEnabled") 为 TRUE 时才支持。
第一种状况下,当浏览器不支持Cookie功能时,浏览器会将用户的SessionCookieName重写到用户请求的URL参数中,它的传递格式如/path/Servlet;name=value;name2=value2?Name3=value3,其中“Servlet;”后面的K-V就是要传递的Path Parameters,服务器会从这个Path Parameters中拿到用户配置的SessionCookieName。关于这个SessionCookieName,若是在web.xml中配置session-config配置项,其cookie-config下的 name属性就是这个SessionCookieName值。若是没有配置了session-config配置项,默认的 SessionCookieName就是你们熟悉的“JSESSIONID”。须要说明的一点是,与Session关联的 Cookie与其余Cookie没有什么不一样。接着Request根据这个SessionCookieName到Parameters 中拿到Session ID并设置到request.setRequestedSessionId中。
Session被建立的同时会产生一个ID号。而后服务器端返回响应的时候,响应消息中的cookie会带一个Jsessionid=“...”。
请注意,若是客户端也支持Cookie,Tomcat仍然会解析Cookie中的Session ID,并会覆盖URL中的Session ID。
若是是第三种状况,将会根据javax.servlet.request.ssl_session属性值设置Session ID。
Session的生命周期是发呆时间,若是咱们设置Session是10s,是指,在10s内,没有访问过session,session中属性失效,若是在9s的时候,你。。。
有了Session ID,服务器端就能够建立HttpSession类的对象了,第一次触发经过request.getSession()方法。若是当前的Session ID尚未对应的HttpSession对象,那么就建立一个新的,并将这个对象加到org.apache.catalina. Manager的sessions容器中保存。Manager 类将管理全部Session的生命周期,Session过时将被回收,服务器关闭,Session 将被序列化到磁盘等。只要这个HttpSession对象存在,用户就能够根据Session ID来获取这个对象,也就达到了状态的保持。
Session相关类图如图 10-4 所示。
图10-4.Session相关类图
从图10-4中能够看出,从request.getSession中获取的HttpSession类的对象其实是 StandardSession对象的门面对象,这与前面的Request和Servlet是同样的原理。图10-5是 Session工做的时序图。
图 10-5.Session工做的时序图(查看大图)
从时序图中能够看出,从 Request中获取的Session对象保存在org.apache. catalina.Manager 类中,它的实现类是org.apache.catalina.session.StandardManager,经过requestedSessionId 从StandardManager的sessions集合中取出StandardSession对象。因为一个requestedSessionId对应一个访问的客户端,因此一个客户端,也就对应一个 StandardSession对象,这个对象正是保存咱们建立的 Session 值的。下面咱们看一下 StandardManager这个类是如何管理StandardSession的生命周期的。
图10-6.StandardManager与StandardSession的类关系图
StandardManager类负责Servlet容器中全部的StandardSession对象的生命周期管理。当Servlet容器重启或关闭时StandardManager负责持久化没有过时的StandardSession对象,它会将全部的StandardSession对象持久化到一个以“SESSIONS.ser”为文件名的文件中。到Servlet容器重启时,也就是StandardManager初始化时,会从新读取这个文件解析出全部Session对象,从新保存在StandardManager的sessions集合中。Session恢复状态图如图10-7所示。
图10-7.Session恢复状态图
当Servlet容器关闭时StandardManager 类会调用unload方法将sessions集合中的StandardSession对象写到“SESSIONS.ser”文件中,而后在启动时再按照上面的状态图从新恢复,注意要持久化保存 Servlet 容器中的 Session 对象,必须调用 Servlet 容器的 stop 和 start 命令,而不能直接结束(kill)Servlet 容器的进程,由于直接结束进程,Servlet 容器没有机会调用 unload 方法来持久化这些 Session 对象。
另外,StandardManager 中的 sessions 集合中的 StandardSession 对象并非永远保存的,不然 Servlet 容器的内存将很容易被消耗尽,因此必须给每一个 Session 对象定义一个有效时间,超过这个时间 Session 对象将被清除。在 Tomcat 中这个有效时间是 60(maxInactiveInterval 属性控制)秒,超过 60 秒该 Session 将会过时。检查每一个 Session 是否失效是在 Tomcat 的一个后台线程中完成的(backgroundProcess() 方法中)。过时 Session 状态图如图 10-8 所示。
图 10-8.过时 Session 状态图
除了后台进程检查 Session 是否失效外,当调用 request.getSession() 时也会检查该 Session 是否过时。值得注意的是,request.getSession() 方法调用的 StandardSession 永远都会存在,即便与这个客户端关联的 Session 对象已通过期。若是过时,又会从新建立一个全新的 StandardSession 对象,可是之前设置的 Session 值将会丢失。若是您取到了 Session 对象可是经过 session.getAttribute 取不到前面设置的 Session 值,请不要奇怪,由于极可能它已经失效了,请检查一下 <Manager pathname="" maxInactiveInterval="60" /> 中 maxInactiveInterval 配置项的值,若是不想让 Session 过时能够设置为 -1。可是您要仔细评估一下,网站的访问量和设置的 Session 的大小,防止将您的 Servlet 容器内存撑爆。若是不想自动建立 Session 对象,也能够经过 request.getSession(boolean create) 方法来判断该客户端关联的 Session 对象是否存在。
虽然 Cookie 和 Session 均可以跟踪客户端的访问记录,可是它们的工做方式显然是不一样的,Cookie 经过把全部要保存的数据经过 HTTP 协议的头部从客户端传递到服务端,又从服务端再传回到客户端,全部的数据都存储在客户端的浏览器里,因此这些 Cookie 数据能够被访问到,就像咱们前面经过 Firefox 的插件 HttpFox 能够看到全部的 Cookie 值。不只能够查看 Cookie,甚至能够经过 Firecookie 插件添加、修改 Cookie,因此 Cookie 的安全性受到了很大的挑战。
相比较而言 Session 的安全性要高不少,由于 Session 是将数据保存在服务端,只是经过 Cookie 传递一个 SessionID 而已,因此 Session 更适合存储用户隐私和重要的数据。
从前面的分析可知,Session和Cookie各自有优势和缺点。在大型互联网系统中,单独使用Cookie和Session都是不可行的,缘由很简单。由于若是使用Cookie,能够很好地解决应用的分布式部署问题,大型互联网应用系统一个应用有上百台机器,并且有不少不一样的应用系统协同工做,因为Cookie是将值存储在客户端的浏览器里,用户每次访问都会将最新的值带回给处理该请求的服务器,因此也就解决了同一个用户的请求可能不在同一台服务器处理而致使的 Cookie不一致的问题。
这种“谁家的孩子谁抱走”的处理方式的确是大型互联网的一个比较简单可是的确能够解决问题的处理方式,可是这种处理方式也会带来了不少其余问题,如:
客户端Cookie存储限制。随着应用系统的增多Cookie数量也快速增长,但浏览器对于用户Cookie的存储是有限制的。例如,IE7以前的IE浏览器,Cookie个数的限制是20个,后续的版本,包括Firefox等,Cookie个数的限制都是50个。总大小不超过 4KB,超过限制就会出现丢弃Cookie的现象发生,这会严重影响应用系统的正常使用。
Cookie管理的混乱。在大型互联网应用系统中,若是每一个应用系统都本身管理每一个应用使用的Cookie,将会致使混乱,因为一般应用系统都在同一个域名下,Cookie 又有上面一条提到的限制,因此没有统一管理很容易出现Cookie超出限制的状况。
安全使人担心。虽然能够经过设置 HttpOnly 属性防止一些私密 Cookie 被客户端访问,可是仍然不能保证 Cookie 没法被篡改。为了保证 Cookie 的私密性一般会对 Cookie 进行加密,可是维护这个加密 Key 也是一件麻烦的事情,没法保证按期来更新加密 Key 也是带来安全性问题的一个重要因素。
当以上问题不能再容忍下去的时候,就不得不想其余办法处理了。
既然 Cookie 有以上这些问题,Session 也有它的好处,为什么不结合使用 Session 和 Cookie 呢?下面是分布式 Session 框架能够解决的问题:
Session 配置的统一管理。
Cookie 使用的监控和统一规范管理。
Session 存储的多元化。
Session 配置的动态修改。
Session 加密 key 的按期修改。
充分的容灾机制,保持框架的使用稳定性。
Session 各类存储的监控和报警支持。
Session 框架的可扩展性,兼容更多的 session 机制如 wapSession。
跨域名 Session 与 Cookie 如何共享,如今同一个网站可能存在多个域名,如何将 Session 和 Cookie 在不一样的域名之间共享是一个具备挑战性的问题。
分布式Session框架的架构图如图 10-9 所示。
为了达成上面所说的几点目标,咱们须要一个服务订阅服务器,在应用启动时能够从这个订阅服务器订阅这个应用须要的可写 Session 项和可写 Cookie 项,这些配置的 Session 和 Cookie 能够限制这个应用可以使用哪些 Session 和 Cookie,甚至能够控制 Session 和 Cookie 可读或者可写。这样能够精确地控制哪些应用能够操做哪些 Session 和 Cookie,能够有效控制 Session 的安全性和 Cookie 的数量。
图 10-9.Session 框架的架构图
如Session的配置项能够为以下形式:
<session> <key>sessionID</key> <ookiekey>sessionID</ookiekey > <lifeCycle>9000</lifeCycle> <base64>true</base64> </session >
Cookie的配置能够为以下形式:
<cookie> <key>cookie</key> <lifeCycle></lifeCycle> <type>1</type> <path>/wp</path> <domain>xulingbo.net</ domain> <decrypt>false</decrypt> <httpOnly>false</ httpOnly > </cookie>
统一经过订阅服务器推送配置能够有效地集中管理资源,因此能够省去每一个应用都来配置 Cookie,简化 Cookie 的管理。若是应用要使用一个新增 Cookie,能够经过一个统一的平台来申请,申请经过才将这个配置项增长到订阅服务器。若是是一个全部应用都要使用的全局 Cookie,那么只需将这个Cookie经过订阅服务器统一推送过去就好了,省去了要在每一个应用中手动增长Cookie的配置。
关于这个订阅服务器如今有不少开源的配置服务器,如 Zookeeper 集群管理服务器,能够统一管理全部服务器的配置文件。
因为应用是一个集群,因此不可能将建立的 Session 都保存在每台应用服务器的内存中,由于若是每台服务器有几十万的访问用户,服务器的内存确定不够用,即便内存够用,这些 Session 也没法同步到这个应用的全部服务器中。因此要共享这些 Session 必须将它们存储在一个分布式缓存中,能够随时写入和读取,并且性能要很好才能知足要求。当前能知足这个要求的系统有不少,如 MemCache 或者淘宝的开源分布式缓存系统 Tair 都是很好的选择。
解决了配置和存储问题,下面看一下如何存取 Session 和 Cookie。
既然是一个分布式 Session 的处理框架,必然会从新实现 HttpSession 的操做接口,使得应用操做 Session 的对象都是咱们实现的 InnerHttpSession 对象,这个操做必须在进入应用以前完成,因此能够配置一个 filter 拦截用户的请求。
先看一下如何封装 HttpSession 对象和拦截请求,图 10-10 是时序图。
咱们能够在应用的web.xml中配置一个SessionFilter,用于在请求到达MVC框架以前封装 HttpServletRequest和HttpServletResponse对象,并建立咱们本身的InnerHttpSession对象,把它设置到request和response对象中。这样应用系统经过request.getHttpSession() 返回的就是咱们建立的InnerHttpSession对象了,咱们能够拦截response的addCookies设置的 Cookie。
在时序图中,应用建立的全部 Session 对象都会保存在 InnerHttpSession 对象中,当用户的此次访问请求完成时,Session 框架将会把这个 InnerHttpSession 的全部内容再更新到分布式缓存中,以便于这个用户经过其余服务器再次访问这个应用系统。另外,为了保证一些应用对 Session 稳定性的特殊要求能够将一些很是关键的 Session 再存储到 Cookie 中,如当分布式缓存存在问题时,能够将部分 Session 存储到 Cookie 中,这样即便分布式缓存出现问题也不会影响关键业务的正常运行。
图 10-10.HttpSession 拦截请求时序图(查看大图)
还有一个很是重要的问题就是如何处理跨域名来共享 Cookie 的问题。咱们知道 Cookie 是有域名限制的,也就是一个域名下的 Cookie 不能被另外一个域名访问,因此若是在一个域名下已经登陆成功,如何访问到另一个域名的应用且保证登陆状态仍然有效,这个问题大型网站应该常常会遇到。如何 解决这个问题呢?下面介绍一种处理方式,如图 10-11 所示。
图 10-11.跨域名同步 session(查看大图)
从图中能够看出,要实现 Session 同步,须要另一个跳转应用,这个应用能够被一个或者多个域名访问,它的主要功能是从一个域名下取得 sessionID,而后将这个 sessionID 同步到另一个域名下。这个 sessionID 其实就是一个 Cookie,至关于咱们常常遇到的 JSESSIONID,因此要实现两个域名下的 Session 同步,必需要将同一个 sessionID 做为 Cookie 写到两个域名下。
总共 12 步,一个域名不用登陆就取到了另一个域名下的 Session,固然这中间有些步骤还能够简化,也能够作一些额外的工做,如能够写一些须要的 Cookie,而不只仅只传一个 sessionID。
除此以外,该框架还能处理 Cookie 被盗取的问题。如您的密码没有丢失,可是您的帐号却有可能被别人登陆的状况,这种状况极可能就是由于您登陆成功后,您的 Cookie 被别人盗取了,盗取您的 Cookie 的人将您的 Cookie 加入到他的浏览器,而后他就能够经过您的 Cookie 正常访问您的我的信息了,这是一个很是严重的问题。在这个框架中咱们能够设置一个 Session 签名,当用户登陆成功后咱们根据用户的私密信息生成的一个签名,以表示当前这个惟一的合法登陆状态,而后将这个签名做为一个 Cookie 在当前这个用户的浏览器进程中和服务器传递,用户每次访问服务器都会检查这个签名和从服务端分布式缓存中取得的 Session 从新生成的签名是否一致,若是不一致,显然这个用户的登陆状态不合法,服务端将清除这个 sessionID 在分布式缓存中的 Session 信息,让用户从新登陆。
Cookie 是在 HTTP消息的头部,因此一般的gzip和deflate针对HTTP Body的压缩不能压缩 Cookie,若是Cookie量很是大,能够考虑将Cookie也作压缩,压缩方式是将Cookie的多个 k/v 对当作普通的文本,作文本压缩。压缩算法一样可使用 gzip 和 deflate 算法,可是须要注意的一点是,根据 Cookie 的规范,Cookie 中不能包含控制字符,仅仅只能包含 ASCII 码为(34 ~ 126)的可见字符。因此要将压缩后的结果再进行转码,能够进行 Base32 或者 Base64 编码。
能够配置一个 Filter 在页面输出时对 Cookie 进行所有或者部分压缩,以下代码所示:
private void compressCookie(Cookie c, HttpServletResponse res) { try { ByteArrayOutputStream bos = null; bos = new ByteArrayOutputStream(); DeflaterOutputStream dos = new DeflaterOutputStream(bos); dos.write(c.getValue().getBytes()); dos.close(); System.out.println(" before compress length:" + c.getValue(). getBytes().length); String compress = new sun.misc.BASE64Encoder().encode(bos. toByteArray()); res.addCookie(new Cookie("compress", compress)); System.out.println("after compress length:" + compress.getBytes(). length); } catch (IOException e) { e.printStackTrace(); } }
上面的代码是用 DeflaterOutputStream 对 Cookie 进行压缩的,Deflater 压缩后再进行 BASE64 编码,相应地用 InflaterInputStream 进行解压。
private void unCompressCookie(Cookie c) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] compress = new sun.misc.BASE64Decoder().decodeBuffer(new String(c.getValue().getBytes())); ByteArrayInputStream bis = new ByteArrayInputStream(compress); InflaterInputStream inflater = new InflaterInputStream(bis); byte[] b = new byte[1024]; int count; while ((count = inflater.read(b)) >= 0) { out.write(b, 0, count); } inflater.close(); System.out.println(out.toByteArray());; } catch (Exception e) { e.printStackTrace(); } }
2KB 大小的 Cookie 压缩前与压缩后字节数相差 20% 左右,若是您的网站的 Cookie 在 2KB~3KB 左右,一天有 1 亿的 PV,那么一天就可以产生 4TB 的带宽流量了,从节省带宽成原本说压缩仍是颇有必要的。
网站中在不少地方都有表单重复提交问题,一种状况是用户在网速慢的状况下可能会重复提交表单,还有就是恶意用户经过程序来发送恶意请求,在这些状况下都要设计一个防止表单重复提交的机制。
要可以防止表单重复提交,就要标识用户的每一次访问请求,使得每一次访问对服务端来讲都是惟一肯定的。为了标识用户的每次访问请求,能够在用户请求一个表单域时增长一个隐藏表单项,这个表单项的值每次都是惟一的 token,如:
<form id=”form” method=”post”> <input type=hidden name=“crsf_token” value=“xxxx”/> </form>
当用户在请求时生成这个惟一的 token 时,同时将这个 token 保存在用户的 Session 中,等用户提交请求时检查这个 token 和当前的 Session 中保存的 token 是否一致。若是一致,说明没有重复提交,不然用户提交上来的 token 已经不是当前的这个请求的合法 token。其工做过程如图 10-12 所示。
图 10-12.工做过程
图 10-12 是用户发起对表单页面的请求过程,生成惟一的 token 须要一个算法,最简单的就是能够根据一个种子做为 key 生成一个随机数,并保存在 Session 中,等下次用户提交表单时作验证。验证表单的过程如图 10-13 所示。
图 10-13.验证表单的过程
当用户提交表单时会将请求时生成的token带回来,这样就能够和Session中保存的token作对比,从而确认此次表单验证是否合法。
Cookie和Session都是为了保持用户访问的连续状态,之因此要保持这种状态,一方面是为了方便业务实现,另外一方面就是简化服务端程序设计,提升访问性能,可是这也带来了另一些挑战,如安全问题、应用的分布式部署带来的 Session 的同步问题及跨域名Session的同步等一系列问题。本章分析了Cookie和Session的工做原理,并介绍了一致分布式Session的解决方案。
一、摘要
(1)、ServletContext是在服务器。
(2)、Servlet是被全部客户端共享。
(3) 、ServletContext适当Web应用启动的时候,自动建立。
(4)、ServletContext当Web应用关闭/tomcat关闭,对Web应用reload会形成销毁。
Web容器在启动时,它会为每一个Web应用程序都建立一个对应的ServletContext对象,它表明当前Web应用。
ServletContext对象能够经过SerbletConfig.getServletContext方法获取对ServletContext对象的引用,也能够经过this.get ServletContext()来获取其对象的引用。
因为一个Web应用中的全部Servlet共享同一个ServletContext对象,所以Servlet对象之间能够经过ServletContext对象来实现通信。ServletContext对象一般也被称之为context域对象。公共聊天室就会用到它。
查看ServletContext API文档,了解ServletContext对象的功能。
对ServletContext的用法小结:
获取:
this.getServletContext();this.getServletConfig().getServletContext();
添加属性:
servletContext.setAttribute(String,Object);
取出属性:
servletContext.getAttribute("属性名");
删除:
servletContext.removeAttribute("");
二、ServletContext的应用
(1)获取Web应用的温馨化参数
<!-- 若是但愿全部的servlet均可以访问该配置 -->
<context-param>
<param-name>name</param-name>
<param-value>scott</param-value>
</context-param>
如何获取
String val = this.getServletContext().getInitParameter("name");
(2)使用ServletContext实现跳转
// 目前咱们跳转到下一个页面
// 一、response.sendRedirect("/Web应用名/资源名");
// 二、request.getRequestDispatcher("/资源名").forward(request, response);
/*
* 区别:
* 一、sendRedirect 发生在浏览器,getRequestDispatcher 发生在Web服务器。
* 二、若是request.setAttribute("name", "顺平"),但愿下一个页面可使用属性值,则使用getRequestDispatcher。
* 三、若是session.setAttribute("name2", "顺平3"),但愿下一个页面可使用属性值,则两个方法都可以使用,可是建议使用getRequestDispatcher。
* 四、若是咱们但愿跳转到本Web应用外的一个url,此时只能使用sendRedirect。
*/
// 三、这种方法和2一眼
this.getServletContext().getRequestDispatcher("/资源url").forward(request, response);
(3)读取文件,和获取文件全路径。
// 首先读取文件
InputStream inputStream = this.getServletContext().getResourceAsStream("dbinfo.properties");
// 建立Properties
Properties pp = new Properties();
pp.load(inputStream);
若是文件放在src目录下,则使用类加载器
// 若是文件放在src目录下,应该使用类加载器来读取。
InputStream is = Servlet5.class.getClassLoader().getResourceAsStream("dbinfo.properties");
//获取文件全路径
// 如何读取到一个文件的全路径 String path = this.getServletContext().getRealPath("/imgs/tree_mydoc.png"); out.println("path=" + path);