java 重写session_关于session的实现:cookie与url重写
本文討論的語境是java EE servlet。
我們都知道session的實現主要兩種方式:cookie與url重寫,而cookie是首選(默認)的方式,因為各種現代瀏覽器都默認開通cookie功能,但是每種瀏覽器也都有允許cookie失效的設置。
由于瀏覽器默認啟動cookie功能,而且普通客戶一般都不會取消cookie功能。久而久之,我們寫代碼的時候,也就不會在意session的具體實現,其實這里面還是有很多值得注意的地方,尤其在用戶取消cookie功能的情況下。
一 servlet session實現與接口簡要介紹:
servlet規范規定實現session的cookie名稱強制為jsessionid(在servlet 3.0 可以自定義了),在瀏覽器第一次請求的時候,服務器產生一個唯一的id,并把這個id設置給一個名為jsessionid的cookie,然后再通過reponse的addcookie,輸出到瀏覽器端。其實這些東西我們都可以通過debug模式下的去查看服務器,來驗證這些內容;或者用http協議攔截器來查看,因為servlet的所有接口也都是基于http協議的,
下面基于http協議解釋一下:
瀏覽器第一次請求:
GET /cxt/index.do HTTP/1.1
...
由于是第一次請求,所以沒有cookie要推給服務器
服務器返回:
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=3EF0AEC40F2C6C30A0580844C0E6B2E8; Path=/cxt
...
...
...
由于服務器沒發現瀏覽器沒提供任何cookie,服務器不知道是瀏覽器未提供cookie的原因:可能是cookie功能取消了,也可能是第一次訪問。所以服務器生成一個名為jsessionid的cookie,用Set-Cookie來把cookie推給瀏覽器;并且,服務器的servlet在生成html頁面的時候需要用reponse.encodeURL方法來編碼url,該方法其實就是用來實現url重寫功能的,這是因為瀏覽器可能是因為取消cookie功能,而未提供cookie的。服務器為了確保下次提交成功,必須確保生成給瀏覽器端的url帶有jsessionid。
若cookie功能沒取消,則瀏覽器瀏覽器第二次請求:
POST /cxt/login.do;jsessionid=3EF0AEC40F2C6C30A0580844C0E6B2E8 HTTP/1.1
Cookie: JSESSIONID=3EF0AEC40F2C6C30A0580844C0E6B2E8;
...
瀏覽器的下一次請求的時候用http的Cookie屬性把當前domain的Cookie都推給服務器,來表明自己的身份。這次,服務器知道瀏覽器支持cookie功能,servlet不需要再使用reponse.encodeURL來編碼url了
若瀏覽器cookie功能取消,則瀏覽器請求內容為
POST /cxt/login.do?jsessionid=3EF0AEC40F2C6C30A0580844C0E6B2E8 HTTP/1.1
...
服務器在接受到上述內容是,通過url后面的jsessionid參數知道這個請求與上一次請求是同一個session
與session有關的類接口:
HttpServletRequest.getSession
HttpSession
HttpServletResponse.encodeURL
二 實現url重寫的編碼注意點
1.既然瀏覽器可能被取消cookie功能,那么我們輸出給客戶端的代碼中必須要支持url重寫功能,分幾種情況解釋
假如純粹用jsp/servlet來寫,那么我們必須手動用reponse.encodeURL來編碼每一個url,包括的href,form的action,或者其他href
2.使用其它web框架的話,最好消息使用框架提供的輸出href功能,否則會有匪夷所思的結果。
比如用struts,若struts單獨提供了一個tag來實現html:rewrite,比如,在沒有jsessionid cookie的情況下,最后會生成http://localhost:8080/ctx/logout.do;jsessionid=0916FB057C169069;若有jsessionid cookie,則會生成http://localhost:8080/ctx/logout.do。但是我們還注意到html:rewrite還有一個href屬性,假如我們用href屬性的話,則struts不會生成帶有jsessionid參數。
struts中提供url重寫的功能的還有html:link與html:form,比如,這個tag功能與html:rewrite相似,也有href屬性,生成帶有標簽的html內容。,html:form沒有href屬性,只有action屬性。
3.假若沒有使用任何框架,則可以使用jstl提供的url重寫功能
jstl提供了標簽來實現c:url,比如,這個也會根據瀏覽器是否支持cookie,來生成帶有jsessionid屬性的url。
相信通過上面的總結,是我們對怎么使用session與cookie有更深入的認識。記住,在用jsp/servlet實現系統的時候,盡量不要自己寫標簽,最好使用系統框架自帶的標簽來實現,否則瀏覽器取消cookie功能的話,系統不支持url重寫功能。
使用表單隱藏域跟蹤Session重寫URL跟蹤Session
Servlet容器先在客戶端瀏覽器中保存一個Sessin ID,以后在瀏覽器發出的HTTP請求中就會包含這個Session ID,Servlet容器讀取HTTP請求中的Session ID,就能判斷出來自各個瀏覽器進程的HTTP請求屬于哪個會話。這一過程稱為Session跟蹤。如果瀏覽器支持Cookie,Servlet容器就把Session ID作為Cookie保存在瀏覽器中。
如果瀏覽器出于安全的原因,禁用Cookie,不允許服務器像客戶端存放Cookie。Servlet容器可以重寫Web組件的URL,把Session ID添加到URL信息中。
HttpServletResponse接口提供了重寫URL的方法:
public?String?encodeURL(String?url)
public?String?encodeRedirectURL(String?url)
只有在當前Web組件支持Session,并且瀏覽器不支持Cookie的情況下,encodeURL方法才會重寫URL,否則直接返回參數指定的原始URL。
例如:
encodeurl.jsp中包含如下代碼:
"/>
當瀏覽器請求訪問 encodeurl.jsp 文件時,如果當前JSP頁面支持Session,并且瀏覽器不支持Cookie,則上面的鏈接被解析為如下形式:
TOMCAT判斷客戶端瀏覽器是否支持Cookie的依據是請求中是否含有Cookie。盡管客戶端可能會支持Cookie,但是由于第一次請求時不會攜帶任何Cookie(因為并無任何Cookie可以攜帶),URL地址重寫后的地址中仍然會帶有jsessionid。當第二次訪問時服務器已經在瀏覽器中寫入Cookie了,因此URL地址重寫后的地址中就不會帶有jsessionid了。
在Session中禁用Cookie
既然可能客戶瀏覽器不支持Cookie,索性禁止Session使用Cookie,統一使用URL地址重寫會更好一些。
Java Web規范支持通過配置的方式禁用Cookie。
下面舉例說一下怎樣通過配置禁止使用Cookie。
打開項目sessionWeb的WebRoot目錄下的META-INF文件夾(跟WEB-INF文件夾同級,如果沒有則創建),打開context.xml(如果沒有則創建),編輯內容如下:
/META-INF/context.xml
或者修改Tomcat全局的conf/context.xml,修改內容如下:
//context.xml
部署后TOMCAT便不會自動生成名JSESSIONID的Cookie,Session也不會以Cookie為識別標志,而僅僅以重寫后的URL地址為識別標志了。
注意:該配置只是禁止Session使用Cookie作為識別標志,并不能阻止其他的Cookie讀寫。也就是說服務器不會自動維護名為JSESSIONID的Cookie了,但是程序中仍然可以讀寫其他的Cookie。
會話狀態保持,JSESSIONID,COOKIE之間的關系
在服務器端,我們用慣了session.setAttribute("",userInfo)這樣的一行代碼,估計你很少想到:服務器與瀏覽器之間是如何保持會話狀態的。好了,先引用一些文章的精彩片段:
http://www.xxx.com/xxx_app;jsessionid=xxxxxxxxxx?a=x&b=x。
這跟一般的url基本一樣,只有一個地方有區別,那就是“;jessionid=xxxxxxxx”。這個參數有時候有,有時候又沒有,說它是參數可又跟一般傳遞的參數不同,它是緊跟在url后面用分號來分隔的,用一般的request.getParameter()方法還取不到jsessionid。
啟動你的tomcat,打開FireFox(愛得不得了,一定要安裝FireBug),輸入localhost就行,打開firebug,點網絡,你會看到,瀏覽器與服務器會話的信息,給出瀏覽器
(1)第一次請求服務器:
瀏覽器的請求頭信息Hostlocalhost
User-AgentMozilla/5.0 (Windows; U;
Windows NT 5.1; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
Accepttext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Languagezh-cn,zh;q=0.5
Accept-Encodinggzip,deflate
Accept-CharsetGB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive115
Connectionkeep-alive
服務器響應頭信息ServerApache-Coyote/1.1
Set-CookieJSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB;
Path=/
Content-Typetext/html;charset=UTF-8
Content-Languagezh-CN
Content-Length242
DateMon, 28 Jun 2010 02:35:29
GMT
(2)第二次請求服務器:
瀏覽器的請求頭信息Hostlocalhost
User-AgentMozilla/5.0 (Windows; U;
Windows NT 5.1; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
Accepttext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Languagezh-cn,zh;q=0.5
Accept-Encodinggzip,deflate
Accept-CharsetGB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive115
Connectionkeep-alive
CookieJSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB
服務器響應頭信息ServerApache-Coyote/1.1
Content-Typetext/html;charset=UTF-8
Content-Languagezh-CN
Content-Length242
DateMon, 28 Jun 2010 02:37:51
GMT
重復第三次,每四次...第N次請求服務器,瀏覽器和服務器的請求頭信息都是與第二次請求服務器是一樣的。
(3)但是,如果你在服務器端加入如下一行代碼:
Log.info("SessionId:" + request.getSession().getId());
你會看到,當你第一次請求服務器時,就會默認有一個新的session被創建,而且在session的有效時間范圍內,這個輸出值是不會變的,否則,服務器會重新創建一個session,自然,sessionId也就不同了,這段代碼的輸出自然也會不同了。
(4)你必須注意這一點:你用的是瀏覽器與服務器通信:
有一些事情是瀏覽器幫助我們去做了,那就是:當你第一次與服務器通信時,瀏覽器會保存服務器返回的Set-Cookie這個健的值(JSESSIONID=64D21B4D69DFB3041B6375C1932BD6CB),只要你不關閉瀏覽器(徹底關閉,關閉選項卡不算),瀏覽器會從第二次向服務器發出請求開始,一直帶上這個鍵值對,發給服務器。服務器就會知道,這是同一個人(同一個會話)發起的請求。
(5)我們再注意一下:request.setAttribute("sysuser",userInfo)這句話:
當你第一次請求服務器時,這句代碼會根據服務器默認產生的session得到ID,并與sysuser=userInfo這個鍵值對掛上鉤(當然,userInfo可以是任何對象),保證唯一關聯,檢測用戶是否登錄就是這樣實現的。
我一定要聲明一點:保持一個會話與用戶是否登錄是沒有任何關系的。
(6)再次引深一下,如果你用的不是瀏覽器,比如說做J2ME開發,怎樣保持會話呢?
(1)在你寫完這行代碼后:HttpConnection hc = (HttpConnection)Connector.open(httpURL),加入以下代碼:(Constant.sessionID只是一個靜態變量)
//在與服務器通信前設置sessionId,維持唯一的一個會話
if?(Constant.sessionID?!=?null)?{
hc.setRequestProperty("Cookie",?AppContext.CurrentAppContext.sessionID);
}
//在與服務器通信前設置sessionId,維持唯一的一個會話
if (Constant.sessionID != null) {
hc.setRequestProperty("Cookie", AppContext.CurrentAppContext.sessionID);
}
(2)
A:只向服務器讀數據,不向服務寫數據,B:先向服務器寫數據,再從服務器讀數據
對于這兩種情況,只要你第一次打開openDataInputStream(),這可以加入以下代碼(Constant.isLogin只是一個靜態變量boolean):
//每次與服務器通信后,保存?sessionId
String?cookie?=?hc.getHeaderField("Set-Cookie");
if?(cookie?!=?null)?{
String?jsessionId?=?cookie.substring(0,cookie.indexOf(";"));
if(Constant.sessionID?!=?null?&&?!Constant.sessionID.equals(jsessionId)?&&?Constant.isLogin?){
Log.info("sessionid超時,?will?get?new?sessionid,?but?you?must?login?again");
//設置為未登錄狀態
Constant.isLogin?=?false;
}
Constant.sessionID?=?jsessionId;
}
//每次與服務器通信后,保存 sessionId
String cookie = hc.getHeaderField("Set-Cookie");
if (cookie != null) {
String jsessionId = cookie.substring(0,cookie.indexOf(";"));
if(Constant.sessionID != null && !Constant.sessionID.equals(jsessionId) && Constant.isLogin ){
Log.info("sessionid超時, will get new sessionid, but you must login again");
//設置為未登錄狀態
Constant.isLogin = false;
}
Constant.sessionID = jsessionId;
}
這樣就可以保持一個會話了。
(7)最后,關于URL重定向
引用一段話:sun幫我們想到了,所以提供了2個方法來使事情變得簡單:response.encodeURL()和response.encodeRedirectURL()。這2個方法會判斷cookie是否可用,如果禁用了會解析出url中的jsessionid,并連接到指定的url后面,如果沒有找到jessionid會自動幫我們生成一個。至于為什么要有2個方法?這2個方法有什么不同?google了一下,說是這2個方法在判斷是否要包含jsessionid的邏輯上會稍有不同。在調用 HttpServletResponse.sendRedirect前,應該先調用encodeRedirectURL()方法,否則可能會丟失 Sesssion信息。這2個方法的使用方法如:response.sendRedirect(response.encodeURL("/myapp /input.jsp"));。如果cookie沒有禁用,我們在瀏覽器地址欄中看到的地址是這樣的:/myapp/input.jsp,如果禁用了 cookie,我們會看到:/myapp /input.jsp;jsessionid=73E6B2470C91A433A6698C7681FD44F4。所以,我們在寫web應用的時候,為了保險起見,應該在程序里的每一個跳轉url上都使用這2個方法,來保證session的可用性。
總結
以上是生活随笔為你收集整理的java 重写session_关于session的实现:cookie与url重写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java技术学习内容_Java开发主要都
- 下一篇: java webservice 服务器_