什么是Session對(duì)Tomcat而言,Session是一塊在服務(wù)器開辟的內(nèi)存空間,其存儲(chǔ)結(jié)構(gòu)為ConcurrentHashMap; Session的目的Http協(xié)議是一種無狀態(tài)協(xié)議,即每次服務(wù)端接收到客戶端的請(qǐng)求時(shí),都是一個(gè)全新的請(qǐng)求,服務(wù)器并不知道客戶端的歷史請(qǐng)求記錄; Session的主要目的就是為了彌補(bǔ)Http的無狀態(tài)特性。簡單的說,就是服務(wù)器可以利用session存儲(chǔ)客戶端在同一個(gè)會(huì)話期間的一些操作記錄; 實(shí)現(xiàn)機(jī)制先看兩個(gè)問題,如下:1、服務(wù)器如何判斷客戶端發(fā)送過來的請(qǐng)求是屬于同一個(gè)會(huì)話? 答:用Session id區(qū)分,Session id相同的即認(rèn)為是同一個(gè)會(huì)話,在Tomcat中Session id用JSESSIONID表示; 2、服務(wù)器、客戶端如何獲取Session id?Session id在其之間是如何傳輸?shù)哪兀?/strong> 答:服務(wù)器第一次接收到請(qǐng)求時(shí),開辟了一塊Session空間(創(chuàng)建了Session對(duì)象),同時(shí)生成一個(gè)Session id,并通過響應(yīng)頭的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客戶端發(fā)送要求設(shè)置cookie的響應(yīng); 客戶端收到響應(yīng)后,在本機(jī)客戶端設(shè)置了一個(gè)JSESSIONID=XXXXXXX的cookie信息,該cookie的過期時(shí)間為瀏覽器會(huì)話結(jié)束; 接下來客戶端每次向同一個(gè)網(wǎng)站發(fā)送請(qǐng)求時(shí),請(qǐng)求頭都會(huì)帶上該cookie信息(包含Session id); 然后,服務(wù)器通過讀取請(qǐng)求頭中的Cookie信息,獲取名稱為JSESSIONID的值,得到此次請(qǐng)求的Session id; ps:服務(wù)器只會(huì)在客戶端第一次請(qǐng)求響應(yīng)的時(shí)候,在響應(yīng)頭上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下來在同一個(gè)會(huì)話的第二第三次響應(yīng)頭里,是不會(huì)添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的; 而客戶端是會(huì)在每次請(qǐng)求頭的cookie中帶上JSESSIONID信息; 舉個(gè)例子:以chrome瀏覽器為例,訪問一個(gè)基于tomcat服務(wù)器的網(wǎng)站的時(shí)候, 瀏覽器第一次訪問服務(wù)器,服務(wù)器會(huì)在響應(yīng)頭添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客戶端設(shè)置cookie,如下圖: 同時(shí)我們也可以在瀏覽器中找到其存儲(chǔ)的sessionid信息,如下圖 接下來,瀏覽器第二次、第三次...訪問服務(wù)器,觀察其請(qǐng)求頭的cookie信息,可以看到JSESSIONID信息存儲(chǔ)在cookie里,發(fā)送給服務(wù)器;且響應(yīng)頭里沒有Set-Cookie信息,如下圖: 只要瀏覽器未關(guān)閉,在訪問同一個(gè)站點(diǎn)的時(shí)候,其請(qǐng)求頭Cookie中的JSESSIONID都是同一個(gè)值,被服務(wù)器認(rèn)為是同一個(gè)會(huì)話。 再舉個(gè)簡單的例子加深印象,新建個(gè)Web工程,并寫一個(gè)Servlet,在doGet中添加如下代碼,主要做如下工作首先,從session中獲取key為count的值,累加,存入session,并打印; 然后,每次從請(qǐng)求中獲取打印cookie信息,從響應(yīng)中獲取打印Header的Set-Cookie信息: /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if(request.getSession().getAttribute("count") == null){ request.getSession().setAttribute("count", 0); response.getWriter().write(0+""); }else{ int a = Integer.parseInt(request.getSession().getAttribute("count").toString()); request.getSession().setAttribute("count", ++a); response.getWriter().write(a+""); } Cookie[] cookies = request.getCookies(); StringBuffer sb = new StringBuffer(); if(cookies!=null){ for(Cookie cookie : cookies){ sb.append(cookie.getName()+":"+cookie.getValue()+","); } sb.deleteCharAt(sb.length()-1); } System.out.println("[第"+(++index)+"次訪問]from client request, cookies:" + sb); System.out.println("[第"+(index)+"次訪問]from server response, header-Set-Cookie:" + response.getHeader("Set-Cookie"));; } 部署到tomcat后,連續(xù)訪問該servlet,觀察控制臺(tái)輸出,如下,客戶端第一次訪問服務(wù)器的時(shí)候,在服務(wù)端的響應(yīng)頭里添加了JSESSIONID信息,且接下來客戶端的每次訪問都會(huì)帶上該JSESSIONID: 其實(shí)這里有一個(gè)問題,session劫持只要用戶知道JSESSIONID,該用戶就可以獲取到JSESSIONID對(duì)應(yīng)的session內(nèi)容,還是以上面這個(gè)例子為例, 我先用IE瀏覽器訪問該站點(diǎn),比如連續(xù)訪問了5次,此時(shí),session中的count值為: 查看該會(huì)話的Session id,為6A541281A79B24BC290ED3270CF15E32 接下來打開chrome控制臺(tái),將IE瀏覽器獲取過來的JSESSIONID信息(“6A541281A79B24BC290ED3270CF15E32”)寫入到cookie中,如下 接著刪除其中的一個(gè),只留下JSESSIONID為“6A541281A79B24BC290ED3270CF15E32”的cookie; 刷新頁面,發(fā)現(xiàn)我們從session獲取的count值已經(jīng)變成6了,說明此次chrome瀏覽器的請(qǐng)求劫持了IE瀏覽器會(huì)話中的session, Tomcat中的session實(shí)現(xiàn)Tomcat中一個(gè)會(huì)話對(duì)應(yīng)一個(gè)session,其實(shí)現(xiàn)類是StandardSession,查看源碼,可以找到一個(gè)attributes成員屬性,即存儲(chǔ)session的數(shù)據(jù)結(jié)構(gòu),為ConcurrentHashMap,支持高并發(fā)的HashMap實(shí)現(xiàn); /** * The collection of user data attributes associated with this Session. */ protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>(); 那么,tomcat中多個(gè)會(huì)話對(duì)應(yīng)的session是由誰來維護(hù)的呢?ManagerBase類,查看其代碼,可以發(fā)現(xiàn)其有一個(gè)sessions成員屬性,存儲(chǔ)著各個(gè)會(huì)話的session信息: /** * The set of currently active Sessions for this Manager, keyed by * session identifier. */ protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>(); 接下來,看一下幾個(gè)重要的方法, 服務(wù)器查找Session對(duì)象的方法客戶端每次的請(qǐng)求,tomcat都會(huì)在HashMap中查找對(duì)應(yīng)的key為JSESSIONID的Session對(duì)象是否存在,可以查看Request的doGetSession方法源碼,如下源碼: ![]() 先看doGetSession方法中的如下代碼,這個(gè)一般是第一次訪問的情況,即創(chuàng)建session對(duì)象,session的創(chuàng)建是調(diào)用了ManagerBase的createSession方法來實(shí)現(xiàn)的; 另外,注意response.addSessionCookieInternal方法,該方法的功能就是上面提到的往響應(yīng)頭寫入“Set-Cookie”信息;最后,還要調(diào)用session.access方法記錄下該session的最后訪問時(shí)間,因?yàn)閟ession是可以設(shè)置過期時(shí)間的; session = manager.createSession(sessionId); // Creating a new session cookie based on that session if ((session != null) && (getContext() != null) && getContext().getServletContext(). getEffectiveSessionTrackingModes().contains( SessionTrackingMode.COOKIE)) { Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie( context, session.getIdInternal(), isSecure()); response.addSessionCookieInternal(cookie); } if (session == null) { return null; } session.access(); return session; 再看doGetSession方法中的如下代碼,這個(gè)一般是第二次以后訪問的情況,通過ManagerBase的findSession方法查找session,其實(shí)就是利用map的key從ConcurrentHashMap中拿取對(duì)應(yīng)的value,這里的key即requestedSessionId,也即JSESSIONID,同時(shí)還要調(diào)用session.access方法,記錄下該session的最后訪問時(shí)間; if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return (session);
}
}
在session對(duì)象中查找和設(shè)置key-value的方法這個(gè)我們一般調(diào)用getAttribute/setAttribute方法: getAttribute方法很簡單,就是根據(jù)key從map中獲取value; setAttribute方法稍微復(fù)雜點(diǎn),除了設(shè)置key-value外,如果添加了一些事件監(jiān)聽(HttpSessionAttributeListener)的話,還要通知執(zhí)行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。 session存在的問題
|
|