摘自 <<精通 Java Web 開發(fā)>>
1 Session的作用
Session 用于維護同一客戶端發(fā)送的多個請求的狀態(tài)驮捍。
由于 HTTP 協(xié)議是無狀態(tài)協(xié)議俗他,從服務器的角度來看娃肿,客戶端瀏覽器與服務器間建立起 socket 連接之后欠橘,就是一個請求的開始加勤,而服務器發(fā)送最后一個數(shù)據(jù)包到客戶端,然后關(guān)閉連接鲤嫡。之后就沒有客戶端與服務器間的連接了逛球,再之后即使是相同客戶端過來的請求,也是通過一條新的連接發(fā)送的屯远。這樣便無法區(qū)分當前請求和以前請求是否是同一個客戶端發(fā)送過來的蔓姚。
如果從現(xiàn)實生活中來看,就好比是去買東西慨丐,每次只能買一件坡脐,買了結(jié)賬就相當于獲取了請求。然后再買再結(jié)賬房揭。备闲。。這樣很麻煩捅暴。所以好的辦法是將東西放入購物車恬砂,一起結(jié)賬。不必每次都去結(jié)賬蓬痒。
購物車在這里起到的就是一個狀態(tài)維護的角色泻骤,這樣的任務也是Session想達成的。
Session的一些典型應用場景:
- 記錄用戶
- 支持工作流
實現(xiàn)Session的兩種途徑:
- Session cookie
- URL重寫
不過總體流程都是:
B第一次提出請求 --->服務器收到請求梧奢,若客戶端沒有發(fā)送Session Cookie過來狱掂,就認為它是第一次請求 ---->通過Session機制為客戶端分配一個Session,并生成對應的Session ID --->將生成的ID返還給客戶端(通過Cookie機制或重寫URL加入Session ID)
相同B的以后請求 --->服務器收到亲轨,檢查有 Session趋惨,查詢 Session ID ---> 獲取對應的存放在服務器的 Session --->進行狀態(tài)維護。
而 Session Cookie 或重寫的 URL 只負責攜帶 Session ID惦蚊,Session 對象由服務器存儲器虾。
當設(shè)置JSP中page指令的session屬性為false時,訪問該頁面就不會產(chǎn)生Session Cookie了养筒。
<%@ page session="false" %>
并且可以在web.xml中對session cookie的生命期進行設(shè)置曾撤。
默認的生命期是當本次會話結(jié)束(關(guān)閉瀏覽器)
2 安全問題
session的使用中很大可能受到session劫持端姚。
- 復制session晕粪,別人就可以通過這個session id訪問到你的內(nèi)容了。
- 讓別人通過一個構(gòu)造的夾雜由session id的url訪問渐裸,則可以通過相同id來之后查看內(nèi)容巫湘。
- 跨站腳本攻擊是使用javascript腳本來讀取本地cookie,然后再在自己本地構(gòu)造一個仿冒的session cookie(或使用url內(nèi)嵌session id)昏鹃。(防范就是可以使用httponly, chrome中的這個選項解釋地不錯:Accessible to script:No (HttpOnly)尚氛,即啟用httponly就是不允許腳本訪問該cookie,而只能是url或鏈接才可以訪問洞渤。
- 中間人攻擊:它是通過一個中間人觀察到請求和響應阅嘶。不過使用HTTPS可以很好防范這樣的偷窺。。讯柔。不過一個突出問題是當網(wǎng)站非純HTTPS(即使是從HTTP直接重定向HTTPS)抡蛙,第一次請求的Session也是通過HTTP發(fā)送,而非加密的HTTPS魂迄。
最佳防范:
- 使用SSL/TLS Session ID:SSL協(xié)議定義了它自己的Session id類型粗截。SSL Session ID在SSL握手的時候建立。然后被用于以后的請求捣炬,它被用于決定使用哪個key來加密和解密熊昌。而且SSL Session ID即不使用Cookie存儲,也不使用URL內(nèi)嵌湿酸。(具體參見RFC2246)這樣也就避免了使用Cookie或URL內(nèi)嵌所造成的安全問題婿屹。
但之前版本的JAVA EE實現(xiàn)SSL Session十分困難,直到EE 6才提供了一個簡單的途徑:使用secure標志的Cookie推溃。它就是SSL Session Cookie选泻。不過它要求你的站點必須始終使用的是HTTPS協(xié)議。
使用SSL Session的另外一個問題是web容器必須能夠支撐SSL交流美莫。如果架構(gòu)的前端使用的是一個web服務器或者負載均衡服務器页眯,則web容器是無法得知請求的SSL Session ID的。在這樣的分布式環(huán)境下使用SSL Session ID厢呵,則用戶請求就還必須總是被路由到web容器(而不是去攔截區(qū)分動態(tài)靜態(tài)請求)窝撵。
最后,SSL Session ID的生命期可能很長襟铭,也可能很短碌奉,這是基于服務器和瀏覽器決定的。所以目前它仍然無法大規(guī)模替代HTTP Session ID寒砖。
3 在服務器端Session對象中存放數(shù)據(jù)
首先來看看如何在web.xml中配置Session:
<session-config>
// 略
</session-config>
4 Session的其他用途:
用戶登陸:可以在session中加入已登陸的session attribute赐劣。
用戶登出:session的invalid方法。
5 使用Listener來監(jiān)聽session變化
實現(xiàn)HttpSessionXXXListener接口哩都,然后在web.xml中寫listener標簽或為該類添加WebListener注解即可魁兼。
監(jiān)聽Session創(chuàng)建銷毀,session屬性改變漠嵌,id改變等等咐汞。(其中改變id是通過request的changesessionid來做的)
獲取session列表的方案:
由于沒有提供獲取全部session的途徑,所以只能是當服務器開啟后監(jiān)聽session的創(chuàng)建儒鹿,然后將session放入一個數(shù)據(jù)結(jié)構(gòu)中保持化撕。以便查詢了。约炎。植阴。編程時貌似是只有這樣的蟹瘾。
6 利用Session來支持分布式應用
要搞企業(yè)級應用,就需要使用分布式掠手∪惹郏可以讓應用得到很好支持。前端可以由很多個不同的負載均衡惨撇,也有很多應用服務器伊脓,但是數(shù)據(jù)服務器必須是同步的。分布式的數(shù)據(jù)的同步問題魁衙,還是抽象成多個讀者一個寫者的讀者寫者問題吧报腔。
但搞分布式的一個挑戰(zhàn)就是:在不同應用服務器上運行的應用組成部分之間的信息交換問題。所以之前出現(xiàn)了許多的解決方案剖淀,比如AMQP纯蛾,JMS,MSMQ等協(xié)議纵隔,都是解決這個問題的翻诉。當然,分布式應用需要解決的問題絕不僅僅只有這一個捌刮。
下面先來看看如何在分布式應用中使用Session ID碰煌。
6.1 在分布式應用中使用Session ID們 :)
目前的情況是:一個特定的Session只會存在與一個單獨的應用服務器內(nèi)存中,并且作為一個單獨的對象存在绅作。
在負載均衡服務器和應用服務器協(xié)同工作的場景下芦圾,可能同一客戶端的兩次連續(xù)請求會被分發(fā)到不同的應用服務器中處理,造成第一個請求被服務器A分配一個Session ID俄认,第二個請求被送達服務器時个少,攜帶的是這個服務器上不存在的Session ID,故第二個服務器又重新為第二個請求分配新的Session ID眯杏。也就是說每次請求被送到不同服務器上夜焦,只要該請求攜帶的Session ID不是這個服務器分配的,則請求相對于服務器而言就是新的一個客戶的請求岂贩,那這樣的話要Session有毛用茫经。。河闰。
一個解決方案是:sticky session科平。意思是讓負載均衡機制能夠識別session id并將對應請求轉(zhuǎn)發(fā)給對應的分配session id的應用服務器處理。這樣的機制也是比較容易實現(xiàn)的姜性,比如先讓負載均衡服務器可以讀取session-cookie,這樣來判斷該cookie對應的應用服務器從而進行轉(zhuǎn)發(fā)髓考〔磕睿或者是負載均衡服務器增加自己的session-cookie到響應中。(不懂)
但這個解決方案存在問題:阻礙使用SSL/HTTPS的使用。因為使用它們后儡炼,負載均衡服務器就無法再對請求進行修改或截獲了妓湘。但是可以讓負載均衡服務器來實現(xiàn)HTTPS/SSL通信。
Tomcat環(huán)境實現(xiàn)負載均衡的措施是在前端使用一個Apache HTTPD或IIS作為web服務器乌询,將請求進行負載均衡到后端的Tomcat服務器實例上榜贴。而Tomcat Connector 提供了Tomcat與web服務器間的通信連接。Connector的mod_jk組件是Apache HTTPD的一個模塊妹田,該模塊將動態(tài)請求轉(zhuǎn)發(fā)給后端Tomcat服務器唬党,并提供sticky session支持(即上面說的將對應session的請求發(fā)送給對應的服務器,而利用的就是Session cookie中的Session ID們鬼佣。驶拱。。)晶衷。
與mod_jk類似蓝纲,IIS上的connector有一個isapi_redirect組件,也是提供相似功能晌纫。
當請求數(shù)目進一步提升時税迷,還可以直接在web服務器前端加入一個簡單的輪詢負載均衡服務器,將請求分配給不同的web服務器(這些web服務器再將負載進一步轉(zhuǎn)發(fā)給不同的應用服務器)锹漱。
上面提到的這種分層結(jié)構(gòu)可以實現(xiàn)很高的擴展性翁狐。
connector使用的是Tomcat中的一個概念:session id jvmroute。這個概念要解決的問題就是將哪個請求轉(zhuǎn)發(fā)給哪個對應的Tomcat實例凌蔬,而利用的正是Session ID露懒。
比如有如下的Session id:abc123(實際的當然比這個長得多,這里僅演示)
在負載均衡環(huán)境下砂心,后端有多臺Tomcat應用服務器(Tomcat實例)懈词,每個Tomcat實例都會擁有一個jvmroute配置(在Tomcat的conf/server.xml文件的<Connector>
元素中配置)。而這個jvmroute的值會被附加到每個session id末尾辩诞。
比如此時有三臺Tomcat實例坎弯,每個分別對應jvmroute值為tcin01,tcin02译暂,tcin03抠忘。
不同Tomcat實例生成的session ID就會是如下:
abc123.tcin02 //比如第二臺,就是sessionID.jvmroute的形式
這種情況下當請求到達后的話外永,Connector(HTTPD服務器的mod_jk模塊或IIS的isapi_redirect模塊)就能夠根據(jù)請求攜帶的session id來對應地將請求轉(zhuǎn)發(fā)到不同Tomcat實例崎脉。
若應用是使用HTTPS的,則需要讓web服務器來驗證請求并進行加密解密工作伯顶。而使用mod_jk或isapi_redirect模塊好處是:它們能夠訪問SSL Session ID并將ID傳送給Tomcat囚灼。Tomcat也可以替換為GlassFish(其他不變)骆膝。
6.2 理解Session復制和失效備緩
sticky session的一個主要問題是:它可以實現(xiàn)高擴展性,但無法實現(xiàn)高可用性灶体。
比如當創(chuàng)建某個session的Tomcat掛掉之后阅签,這個對應的session的用戶就相對于又成了新用戶了,又需要如重新登陸等蝎抽,更有可能是用戶丟失沒有保存的數(shù)據(jù)政钟。正因為如此,下面提出一種解決辦法:session的復制樟结。簡單來說就是將每個應用服務器上的session都復制养交,這樣所有服務器上的所有session對所有服務器都可用。
而在應用用打開session復制功能也很簡單:在web.xml中加入一句 <distributable />
即可狭吼。
這個標簽告訴web容器层坠,若是在分布式環(huán)境下的話,就進行session復制刁笙。(如果某個session發(fā)生改變破花,則這個改變也會重新被復制到每個應用服務器中。
不過設(shè)置這個標簽只是為了向web容器說明該應用支持session復制疲吸。而實際使用時還需要配置web容器座每,開啟容器的session復制機制。
但在開啟了session復制的環(huán)境下摘悴,就必須額外小心地進行session的屬性的設(shè)置或改變等操作了峭梳。(比如session中的某個屬性內(nèi)存放的是一個集合,對這個集合中的元素的改變并不會觸發(fā)session的重新復制蹂喻。故要對其中集合進行操作的話葱椭,最好是在外部操作集合,然后進行一次setAttribute設(shè)置該集合到session中口四,這樣才會觸發(fā)重新復制的動作)
另外要注意一個監(jiān)聽器HttpSessionActivationListener孵运,這個監(jiān)聽器用于監(jiān)聽Session的序列化(因為復制時需要先序列化再反序列化),當進行序列化動作時就會觸發(fā)監(jiān)聽器的sessionWillPassive方法蔓彩。而當該序列再在另外容器中被反序列化時治笨,會觸發(fā)監(jiān)聽器的sessionDidActivate方法。
最后要指出的是:sticky session id機制和Session復制機制并不互斥赤嚼。實際上二者經(jīng)常結(jié)合使用以滿足session的失效備緩旷赖。(session仍然被復制到每個容器,當某容器掛掉后更卒,該容器對應的請求會被轉(zhuǎn)發(fā)給另外的容器等孵,因為另外的容器是可用識別這個session的。)
另外可以使用sticky session failover機制來達到高擴展和高可用逞壁,不過這個現(xiàn)在不作討論流济。一般在web容器文檔可用找到如何實現(xiàn)session復制的配置锐锣。