最近公司开发一个移动端的APP,有幸能够参与。可是客户端与服务端的交互,是没有session和cookies概念的。那么怎么去模拟呢。领导的通讯协议里面写得很清楚,就是为每一个用户建立一个token,其实这个token也就是至关于session。然而我2B的问了一句:“为何不将token持久化到客户端,而是须要服务器维护建立呢。”html
其实,原理是和session差很少的,固然是出于安全性。防止非法用户访问服务器,APP结束时结束token,也能够必定程度起到保护做用。因为每次token不同,并且也有必定的实效性,那么就减小了token被截取的可能性。下面转载一片大话Session。很不错的文章....java
大话Session程序员
在web开发中,session是个很是重要的概念。在许多动态网站的开发者看来,session就是一个变量,并且其表现像个黑洞,他只须要将东西在合适的时机放进这个洞里,等须要的时候再把东西取出来。这是开发者对session最直观的感觉,可是黑洞里的景象或者说session内部究竟是怎么工做的呢?当笔者向身边的一些同事或朋友问及相关的更进一步的细节时,不少人每每要么含糊其辞要么主观臆断,所谓知其然而不知其因此然。web
笔者由此想到不少开发者,包括我本身,往往都是纠缠于框架甚至二次开发平台之上,而对于其下的核心和基础知之甚少,或者有心无力甚至绝不关心,少了逐本溯源的精神,每忆及此,无不惭愧。曾经实现过一个简单的HttpServer,但当时因为知识储备和时间的问题,没有考虑到session这块,不过近期在工做之余翻看了一些资料,并进行了相关实践,小有所得,本着分享的精神,我将在本文中尽量全面地将我的对于session的理解展示给读者,同时尽我所能地论及一些相关的知识,以期读者在对session有所了解的同时也能另有所悟,正所谓授人以渔。算法
Session通常译做会话,牛津词典对其的解释是进行某活动连续的一段时间。从不一样的层面看待session,它有着相似但不全然相同的含义。好比,在web应用的用户看来,他打开浏览器访问一个电子商务网站,登陆、并完成购物直到关闭浏览器,这是一个会话。而在web应用的开发者开来,用户登陆时我须要建立一个数据结构以存储用户的登陆信息,这个结构也叫作session。所以在谈论session的时候要注意上下文环境。而本文谈论的是一种基于HTTP协议的用以加强web应用能力的机制或者说一种方案,它不是单指某种特定的动态页面技术,而这种能力就是保持状态,也能够称做保持会话。数据库
谈及session通常是在web应用的背景之下,咱们知道web应用是基于HTTP协议的,而HTTP协议偏偏是一种无状态协议。也就是说,用户从A页面跳转到B页面会从新发送一次HTTP请求,而服务端在返回响应的时候是没法获知该用户在请求B页面以前作了什么的。apache
对于HTTP的无状态性的缘由,相关RFC里并无解释,但联系到HTTP的历史以及应用场景,咱们能够推测出一些理由:浏览器
1. 设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。那个时候没有动态页面技术,只有纯粹的静态HTML页面,所以根本不须要协议能保持状态;tomcat
2. 用户在收到响应时,每每要花一些时间来阅读页面,所以若是保持客户端和服务端之间的链接,那么这个链接在大多数的时间里都将是空闲的,这是一种资源的无故浪费。因此HTTP原始的设计是默认短链接,即客户端和服务端完成一次请求和响应以后就断开TCP链接,服务器所以没法预知客户端的下一个动做,它甚至都不知道这个用户会不会再次访问,所以让HTTP协议来维护用户的访问状态也全然没有必要;安全
3. 将一部分复杂性转嫁到以HTTP协议为基础的技术之上可使得HTTP在协议这个层面上显得相对简单,而这种简单也赋予了HTTP更强的扩展能力。事实上,session技术从本质上来说也是对HTTP协议的一种扩展。
总而言之,HTTP的无状态是由其历史使命而决定的。但随着网络技术的蓬勃发展,人们不再知足于死板乏味的静态HTML,他们但愿web应用能动起来,因而客户端出现了脚本和DOM技术,HTML里增长了表单,而服务端出现了CGI等等动态技术。
而正是这种web动态化的需求,给HTTP协议提出了一个难题:一个无状态的协议怎样才能关联两次连续的请求呢?也就是说无状态的协议怎样才能知足有状态的需求呢?
此时有状态是必然趋势而协议的无状态性也是木已成舟,所以咱们须要一些方案来解决这个矛盾,来保持HTTP链接状态,因而出现了cookie和session。
对于此部份内容,读者或许会有一些疑问,笔者在此先谈两点:
1. 无状态性和长链接
可能有人会问,如今被普遍使用的HTTP1.1默认使用长链接,它仍是无状态的吗?
链接方式和有无状态是彻底没有关系的两回事。由于状态从某种意义上来说就是数据,而链接方式只是决定了数据的传输方式,而不能决定数据。长链接是随着计算机性能的提升和网络环境的改善所采起的一种合理的性能上的优化,通常状况下,web服务器会对长链接的数量进行限制,以避免资源的过分消耗。
2. 无状态性和session
Session是有状态的,而HTTP协议是无状态的,两者是否矛盾呢?
Session和HTTP协议属于不一样层面的事物,后者属于ISO七层模型的最高层应用层,前者不属于后者,前者是具体的动态页面技术来实现的,但同时它又是基于后者的。在下文中笔者会分析Servlet/Jsp技术中的session机制,这会使你对此有更深入的理解。
上面提到解决HTTP协议自身无状态的方式有cookie和session。两者都能记录状态,前者是将状态数据保存在客户端,后者则保存在服务端。
首先看一下cookie的工做原理,这须要有基本的HTTP协议基础。
cookie是在RFC2109(已废弃,被RFC2965取代)里初次被描述的,每一个客户端最多保持三百个cookie,每一个域名下最多20个Cookie(实际上通常浏览器如今都比这个多,如Firefox是50个),而每一个cookie的大小为最多4K,不过不一样的浏览器都有各自的实现。对于cookie的使用,最重要的就是要控制cookie的大小,不要放入无用的信息,也不要放入过多信息。
不管使用何种服务端技术,只要发送回的HTTP响应中包含以下形式的头,则视为服务器要求设置一个cookie:
Set-cookie:name=name;expires=date;path=path;domain=domain
支持cookie的浏览器都会对此做出反应,即建立cookie文件并保存(也多是内存cookie),用户之后在每次发出请求时,浏览器都要判断当前全部的cookie中有没有没失效(根据expires属性判断)而且匹配了path属性的cookie信息,若是有的话,会如下面的形式加入到请求头中发回服务端:
Cookie: name="zj"; Path="/linkage"
服务端的动态脚本会对其进行分析,并作出相应的处理,固然也能够选择直接忽略。
这里牵扯到一个规范(或协议)与实现的问题,简单来说就是规范规定了作成什么样子,那么实现就必须依据规范来作,这样才能互相兼容,可是各个实现所使用的方式却不受约束,也能够在实现了规范的基础上超出规范,这就称之为扩展了。不管哪一种浏览器,只要想提供cookie的功能,那就必须依照相应的RFC规范来实现。因此这里服务器只管发Set-cookie头域,这也是HTTP协议无状态性的一种体现。
须要注意的是,出于安全性的考虑,cookie能够被浏览器禁用。
再看一下session的原理:
笔者没有找到相关的RFC,由于session本就不是协议层面的事物。它的基本原理是服务端为每个session维护一份会话信息数据,而客户端和服务端依靠一个全局惟一的标识来访问会话信息数据。用户访问web应用时,服务端程序决定什么时候建立session,建立session能够归纳为三个步骤:
1. 生成全局惟一标识符(sessionid);
2. 开辟数据存储空间。通常会在内存中建立相应的数据结构,但这种状况下,系统一旦掉电,全部的会话数据就会丢失,若是是电子商务网站,这种事故会形成严重的后果。不过也能够写到文件里甚至存储在数据库中,这样虽然会增长I/O开销,但session能够实现某种程度的持久化,并且更有利于session的共享;
3. 将session的全局惟一标示符发送给客户端。
问题的关键就在服务端如何发送这个session的惟一标识上。联系到HTTP协议,数据无非能够放到请求行、头域或Body里,基于此,通常来讲会有两种经常使用的方式:cookie和URL重写。
1. Cookie
读者应该想到了,对,服务端只要设置Set-cookie头就能够将session的标识符传送到客户端,而客户端此后的每一次请求都会带上这个标识符,因为cookie能够设置失效时间,因此通常包含session信息的cookie会设置失效时间为0,即浏览器进程有效时间。至于浏览器怎么处理这个0,每一个浏览器都有本身的方案,但差异都不会太大(通常体如今新建浏览器窗口的时候);
2. URL重写
所谓URL重写,顾名思义就是重写URL。试想,在返回用户请求的页面以前,将页面内全部的URL后面所有以get参数的方式加上session标识符(或者加在path info部分等等),这样用户在收到响应以后,不管点击哪一个连接或提交表单,都会在再带上session的标识符,从而就实现了会话的保持。读者可能会以为这种作法比较麻烦,确实是这样,可是,若是客户端禁用了cookie的话,URL重写将会是首选。
到这里,读者应该明白我前面为何说session也算做是对HTTP的一种扩展了吧。以下两幅图是笔者在Firefox的Firebug插件中的截图,能够看到,当我第一次访问index.jsp时,响应头里包含了Set-cookie头,而请求头中没有。当我再次刷新页面时,图二显示在响应中不在有Set-cookie头,而在请求头中却有了Cookie头。注意一下Cookie的名字:jsessionid,顾名思义,就是session的标识符,另外能够看到两幅图中的jsessionid的值是相同的,缘由笔者就再也不多解释了。另外读者可能在一些网站上见过在最后附加了一段形如jsessionid=xxx的URL,这就是采用URL重写来实现的session。
(图一,首次请求index.jsp)
(图二,再次请求index.jsp)
Cookie和session因为实现手段不一样,所以也各有优缺点和各自的应用场景:
1. 应用场景
Cookie的典型应用场景是Remember Me服务,即用户的帐户信息经过cookie的形式保存在客户端,当用户再次请求匹配的URL的时候,帐户信息会被传送到服务端,交由相应的程序完成自动登陆等功能。固然也能够保存一些客户端信息,好比页面布局以及搜索历史等等。
Session的典型应用场景是用户登陆某网站以后,将其登陆信息放入session,在之后的每次请求中查询相应的登陆信息以确保该用户合法。固然仍是有购物车等等经典场景;
2. 安全性
cookie将信息保存在客户端,若是不进行加密的话,无疑会暴露一些隐私信息,安全性不好,通常状况下敏感信息是通过加密后存储在cookie中,但很容易就会被窃取。而session只会将信息存储在服务端,若是存储在文件或数据库中,也有被窃取的可能,只是可能性比cookie小了太多。
Session安全性方面比较突出的是存在会话劫持的问题,这是一种安全威胁,这在下文会进行更详细的说明。整体来说,session的安全性要高于cookie;
3. 性能
Cookie存储在客户端,消耗的是客户端的I/O和内存,而session存储在服务端,消耗的是服务端的资源。可是session对服务器形成的压力比较集中,而cookie很好地分散了资源消耗,就这点来讲,cookie是要优于session的;
4. 时效性
Cookie能够经过设置有效期使其较长时间内存在于客户端,而session通常只有比较短的有效期(用户主动销毁session或关闭浏览器后引起超时);
5. 其余
Cookie的处理在开发中没有session方便。并且cookie在客户端是有数量和大小的限制的,而session的大小却只以硬件为限制,能存储的数据无疑大了太多。
经过上述的讲解,读者应该对session有了一个大致的认识,可是具体到某种动态页面技术,又是怎么实现session的呢?下面笔者将结合session的生命周期(lifecycle),从源代码的层次来具体分析一下在servlet/jsp技术中,session是怎么实现的。代码部分以tomcat6.0.20做为参考。
在我问过的一些从事java web开发的人中,对于session的建立时机大都这么回答:当我请求某个页面的时候,session就被建立了。这句话其实很含糊,由于要建立session请求的发送是必不可少的,可是不管何种请求都会建立session吗?错。咱们来看一个例子。
众所周知,jsp技术是servlet技术的反转,在开发阶段,咱们看到的是jsp页面,但真正到运行时阶段,jsp页面是会被“翻译”为servlet类来执行的,例如咱们有以下jsp页面:
<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>index.jsp</title> </head> <body> This is index.jsp page. <br> </body> </html> |
在咱们初次请求该页面后,在对应的work目录能够找到该页面对应的java类,考虑到篇幅的缘由,在此只摘录比较重要的一部分,有兴趣的读者能够亲自试一下:
...... response.setContentType("text/html;charset=ISO-8859-1"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out;
out.write("\r\n"); out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n"); out.write("<html>\r\n"); ...... |
能够看到有一句显式建立session的语句,它是怎么来的呢?咱们再看一下对应的jsp页面,在jsp的page指令中加入了session="true",意思是在该页面启用session,其实做为动态技术,这个参数是默认为true的,这很合理,在此显示写出来只是作一下强调。很显然两者有着必然的联系。笔者在jsp/servlet的翻译器(org.apache.jasper.compiler)的源码中找到了相关证据:
...... if (pageInfo.isSession()) out.printil("session = pageContext.getSession();"); out.printil("out = pageContext.getOut();"); out.printil("_jspx_out = out;"); ...... |
上面的代码片断的意思是若是页面中定义了session="true",就在生成的servlet源码中加入session的获取语句。这只可以说明session建立的条件,显然还不能说明session是如何建立的,本着逐本溯源的精神,咱们继续往下探索。
有过servlet开发经验的应该记得咱们是经过HttpServletRequest的getSession方法来获取当前的session对象的:
public HttpSession getSession(boolean create); public HttpSession getSession(); |
两者的区别只是无参的getSession将create默认设置为true而已。即:
public HttpSession getSession() { return (getSession(true)); } |
那么这个参数到底意味着什么呢?经过层层跟踪,笔者终于理清了其中的脉络,因为函数之间的关系比较复杂,若是想更详细地了解内部机制,建议去独立阅读tomcat相关部分的源代码。这里我将其中的大体流程叙述一下:
1. 用户请求某jsp页面,该页面设置了session="true";
2. Servlet/jsp容器将其翻译为servlet,并加载、执行该servlet;
3. Servlet/jsp容器在封装HttpServletRequest对象时根据cookie或者url中是否存在jsessionid来决定是绑定当前的session到HttpRequest仍是建立新的session对象(在请求解析阶段发现并记录jsessionid,在Request对象建立阶段将session绑定);
4. 程序按需操做session,存取数据;
5. 若是是新建立的session,在结果响应时,容器会加入Set-cookie头,以提醒浏览器要保持该会话(或者采用URL重写方式将新的连接呈现给用户)。
经过上面的叙述读者应该了解了session是什么时候建立的,这里再从servlet这个层面总结一下:当用户请求的servlet调用了getSession方法时,都会获取session,至因而否建立新的session取决于当前request是否已绑定session。当客户端在请求中加入了jsessionid标识而servlet容器根据此标识查找到了对应的session对象时,会将此session绑定到这次请求的request对象,客户端请求中不带jsessionid或者此jsessionid对应的session已过时失效时,session的绑定没法完成,此时必须建立新的session。同时发送Set-cookie头通知客户端开始保持新的会话。
理解了session的建立,就很好理解会话是如何在客户端和服务端之间保持的了。当首次建立了session后,客户端会在后续的请求中将session的标识符带到服务端,服务端程序只要在须要session的时候调用getSession,服务端就能够将对应的session绑定到当前请求,从而实现状态的保持。固然这须要客户端的支持,若是禁用了cookie而又不采用url重写的话,session是没法保持的。
若是几回请求之间有一个servlet未调用getSession(或者干脆请求一个静态页面)会不会使得会话中断呢?这个不会发生的,由于客户端只会将合法的cookie值传送给服务端,至于服务端拿cookie作什么事它是不会关心的,固然也没法关心。Session创建以后,客户端会一直将session的标识符传送到服务器,不管请求的页面是动态的、静态的,甚至是一副图片。
此处谈到的销毁是指会话的废弃,至于存储会话信息的数据结构是回收被重用仍是直接释放内存咱们并不关心。Session的销毁有两种状况:超时和手动销毁。
因为HTTP协议的无状态性,服务端没法得知一个session对象什么时候将再次被使用,可能用户开启了一个session以后再也没有后续的访问,并且session的保持是须要消耗必定的服务端开销的,所以不可能一味地建立session而不去回收无用的session。这里就引入了一个超时机制。Tomcat中的超时在web.xml里作以下配置:
<session-config> <session-timeout>30</session-timeout> </session-config> |
上述配置是指session在30分钟没有被再次使用就将其销毁。Tomcat是怎么计算这个30分钟的呢?原来在getSession以后,都要调用它的access方法,修改lastAccessedTime,在销毁session的时候就是判断当前时间和这个lastAccessedTime的差值。
手动销毁是指直接调用其invalidate方法,此方法其实是调用expire方法来手动将其设置为超时。
当用户手动请求了session的销毁时,客户端是没法知道服务端的session已经被销毁的,它依然会发送先前的session标识符到服务端。而此时若是再次请求了某个调用了getSession的servlet,服务端是没法根据先前的session标识符找到相应的session对象的,这是又要从新建立新的session,分配新的标识符,并告知服务端更新session标识符开始保持新的会话。
在servlet/jsp中,容器是用何种数据结构来存储session相关的变量的呢?咱们猜想一下,首先它必须被同步操做,由于在多线程环境下session是线程间共享的,而web服务器通常状况下都是多线程的(为了提升性能还会用到池技术);其次,这个数据结构必须容易操做,最好是传统的键值对的存取方式。
那么咱们先具体到单个session对象,它除了存储自身的相关信息,好比id以外,tomcat的session还提供给程序员一个用以存储其余信息的接口(在类org.apache.catalina.session. StandardSession里):
public void setAttribute(String name, Object value, boolean notify) |
在这里能够追踪到它到底使用了何种数据:
protected Map attributes = new ConcurrentHashMap(); |
这就很明确了,原来tomcat使用了一个ConcurrentHashMap对象存储数据,这是java的concurrent包里的一个类。它恰好知足了咱们所猜想的两点需求:同步与易操做性。
那么tomcat又是用什么数据结构来存储全部的session对象呢?果真仍是ConcurrentHashMap(在管理session的org.apache.catalina.session. ManagerBase类里):
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>(); |
具体缘由就没必要多说了。至于其余web服务器的具体实现也应该考虑到这两点。
Session hijack即会话劫持是一种比较严重的安全威胁,也是一种普遍存在的威胁,在session技术中,客户端和服务端经过传送session的标识符来维护会话,但这个标识符很容易就能被嗅探到,从而被其余人利用,这属于一种中间人攻击。
本部分经过一个实例来讲明何为会话劫持,经过这个实例,读者其实更能理解session的本质。
首先,我编写了以下页面:
<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>index.jsp</title> </head> <body> This is index.jsp page. <br> <% Object o = session.getAttribute("counter"); if (o == null) { session.setAttribute("counter", 1); } else { Integer i = Integer.parseInt(o.toString()); session.setAttribute("counter", i + 1); } out.println(session.getAttribute("counter")); %> <a href="<%=response.encodeRedirectURL("index.jsp")%>">index</a> </body> </html> |
页面的功能是在session中放置一个计数器,第一次访问该页面,这个计数器的值初始化为1,之后每一次访问这个页面计数器都加1。计数器的值会被打印到页面。另外,为了比较简单地模拟,笔者禁用了客户端(采用firefox3.0)的cookie,转而改用URL重写方式,由于直接复制连接要比伪造cookie方便多了。
下面,打开firefox访问该页面,咱们看到了计数器的值为1:
(图三)
而后点击index连接来刷新计数器,注意不要刷新当前页,由于咱们没用采用cookie的方式,只能在url后面带上jsessionid,而此时地址栏里的url是没法带上jsessionid的。如图四,我把计数器刷新到了20。
(图四)
下面是最关键的,复制firefox地址栏里的地址(笔者看到的是http://localhost:8080/sessio
n/index.jsp;jsessionid=1380D9F60BCE9C30C3A7CBF59454D0A5),而后打开另外一个浏览器,此处没必要将其cookie禁用。这里我打开了苹果的safari3浏览器,而后将地址粘贴到其地址栏里,回车后以下图:
(图五)
很奇怪吧,计数器直接到了21。这个例子笔者是在同一台计算机上作的,不过即便换用两台来作,其结果也是同样的。此时若是交替点击两个浏览器里的index连接你会发现他们其实操纵的是同一个计数器。其实没必要惊讶,此处safari盗用了firefox和tomcat之间的维持会话的钥匙,即jsessionid,这属于session hijack的一种。在tomcat看来,safari交给了它一个jsessionid,因为HTTP协议的无状态性,它没法得知这个jsessionid是从firefox那里“劫持”来的,它依然会去查找对应的session,并执行相关计算。而此时firefox也没法得知本身的保持会话已经被“劫持”。
到这里,读者应该对session有了更多的更深层次的了解,不过因为笔者的水平以及视野有限,文中也不乏表述欠妥之处,通篇更多地描述了在servlet/jsp中的session机制,但其余开发平台的机制也都万变不离其宗。只要认真思考,你会发现其实这里林林总总之间,总有一些因果关系存在。在软件规模日益增大的背景下,咱们更多的时候接触到的是框架、组件,程序员的双眼被蒙蔽了,在这些框架、组件不断产生以及版本的不断更新中,其实有不少相对不变的东西,那就是规范、协议、模式、算法等等,真正令一我的获得提升的仍是那些底层的支撑技术。平时多多思考的话,你就能把相似的探索转化为印证。作技术犹如解牛,知筋知骨方能游刃有余。
转载请保留出处:shoru.cnblogs.com 晋哥哥的私房钱