Tomcat中實(shí)現(xiàn)Session小結(jié)
什么是Session
對(duì)Tomcat而言,Session是一塊在服務(wù)器開(kāi)辟的內(nèi)存空間,其存儲(chǔ)結(jié)構(gòu)為ConcurrentHashMap;
Session的目的
Http協(xié)議是一種無(wú)狀態(tài)協(xié)議,即每次服務(wù)端接收到客戶端的請(qǐng)求時(shí),都是一個(gè)全新的請(qǐng)求,服務(wù)器并不知道客戶端的歷史請(qǐng)求記錄;
Session的主要目的就是為了彌補(bǔ)Http的無(wú)狀態(tài)特性。簡(jiǎn)單的說(shuō),就是服務(wù)器可以利用session存儲(chǔ)客戶端在同一個(gè)會(huì)話期間的一些操作記錄;
實(shí)現(xiàn)機(jī)制
先看兩個(gè)問(wèn)題,如下:
1、服務(wù)器如何判斷客戶端發(fā)送過(guò)來(lái)的請(qǐng)求是屬于同一個(gè)會(huì)話?
答:用Session id區(qū)分,Session id相同的即認(rèn)為是同一個(gè)會(huì)話,在Tomcat中Session id用JSESSIONID表示;
2、服務(wù)器、客戶端如何獲取Session id?Session id在其之間是如何傳輸?shù)哪兀?/p>
答:服務(wù)器第一次接收到請(qǐng)求時(shí),開(kāi)辟了一塊Session空間(創(chuàng)建了Session對(duì)象),同時(shí)生成一個(gè)Session id,并通過(guò)響應(yīng)頭的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客戶端發(fā)送要求設(shè)置cookie的響應(yīng);
客戶端收到響應(yīng)后,在本機(jī)客戶端設(shè)置了一個(gè)JSESSIONID=XXXXXXX的cookie信息,該cookie的過(guò)期時(shí)間為瀏覽器會(huì)話結(jié)束;
接下來(lái)客戶端每次向同一個(gè)網(wǎng)站發(fā)送請(qǐng)求時(shí),請(qǐng)求頭都會(huì)帶上該cookie信息(包含Session id);
然后,服務(wù)器通過(guò)讀取請(qǐng)求頭中的Cookie信息,獲取名稱(chēng)為JSESSIONID的值,得到此次請(qǐng)求的Session id;
ps:服務(wù)器只會(huì)在客戶端第一次請(qǐng)求響應(yīng)的時(shí)候,在響應(yīng)頭上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下來(lái)在同一個(gè)會(huì)話的第二第三次響應(yīng)頭里,是不會(huì)添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的;
而客戶端是會(huì)在每次請(qǐng)求頭的cookie中帶上JSESSIONID信息;
舉個(gè)例子:
以chrome瀏覽器為例,訪問(wèn)一個(gè)基于tomcat服務(wù)器的網(wǎng)站的時(shí)候,
瀏覽器第一次訪問(wèn)服務(wù)器,服務(wù)器會(huì)在響應(yīng)頭添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客戶端設(shè)置cookie,如下圖:

同時(shí)我們也可以在瀏覽器中找到其存儲(chǔ)的sessionid信息,如下圖

接下來(lái),瀏覽器第二次、第三次...訪問(wèn)服務(wù)器,觀察其請(qǐng)求頭的cookie信息,可以看到JSESSIONID信息存儲(chǔ)在cookie里,發(fā)送給服務(wù)器;且響應(yīng)頭里沒(méi)有Set-Cookie信息,如下圖:

只要瀏覽器未關(guān)閉,在訪問(wèn)同一個(gè)站點(diǎn)的時(shí)候,其請(qǐng)求頭Cookie中的JSESSIONID都是同一個(gè)值,被服務(wù)器認(rèn)為是同一個(gè)會(huì)話。
再舉個(gè)簡(jiǎn)單的例子加深印象,新建個(gè)Web工程,并寫(xiě)一個(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)+"次訪問(wèn)]from client request, cookies:" + sb);
System.out.println("[第"+(index)+"次訪問(wèn)]from server response, header-Set-Cookie:" + response.getHeader("Set-Cookie"));;
}
部署到tomcat后,連續(xù)訪問(wèn)該servlet,觀察控制臺(tái)輸出,如下,客戶端第一次訪問(wèn)服務(wù)器的時(shí)候,在服務(wù)端的響應(yīng)頭里添加了JSESSIONID信息,且接下來(lái)客戶端的每次訪問(wèn)都會(huì)帶上該JSESSIONID:

其實(shí)這里有一個(gè)問(wèn)題,session劫持
只要用戶知道JSESSIONID,該用戶就可以獲取到JSESSIONID對(duì)應(yīng)的session內(nèi)容,還是以上面這個(gè)例子為例,
我先用IE瀏覽器訪問(wèn)該站點(diǎn),比如連續(xù)訪問(wèn)了5次,此時(shí),session中的count值為:

查看該會(huì)話的Session id,為6A541281A79B24BC290ED3270CF15E32

接下來(lái)打開(kāi)chrome控制臺(tái),將IE瀏覽器獲取過(guò)來(lái)的JSESSIONID信息(“6A541281A79B24BC290ED3270CF15E32”)寫(xiě)入到cookie中,如下

接著刪除其中的一個(gè),只留下JSESSIONID為“6A541281A79B24BC290ED3270CF15E32”的cookie;

刷新頁(yè)面,發(fā)現(xiàn)我們從session獲取的count值已經(jīng)變成6了,說(shuō)明此次chrome瀏覽器的請(qǐng)求劫持了IE瀏覽器會(huì)話中的session,

Tomcat中的session實(shí)現(xiàn)
Tomcat中一個(gè)會(huì)話對(duì)應(yīng)一個(gè)session,其實(shí)現(xiàn)類(lèi)是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是由誰(shuí)來(lái)維護(hù)的呢?ManagerBase類(lèi),查看其代碼,可以發(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>();
接下來(lái),看一下幾個(gè)重要的方法,
服務(wù)器查找Session對(duì)象的方法
客戶端每次的請(qǐng)求,tomcat都會(huì)在HashMap中查找對(duì)應(yīng)的key為JSESSIONID的Session對(duì)象是否存在,可以查看Request的doGetSession方法源碼,如下源碼:
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
Context context = getContext();
if (context == null) {
return (null);
}
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return (session);
}
// Return the requested session if it exists and is valid
Manager manager = context.getManager();
if (manager == null) {
return null; // Sessions are not supported
}
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);
}
}
// Create a new session if requested and the response is not committed
if (!create) {
return (null);
}
if ((context != null) && (response != null) &&
context.getServletContext().getEffectiveSessionTrackingModes().
contains(SessionTrackingMode.COOKIE) &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Re-use session IDs provided by the client in very limited
// circumstances.
String sessionId = getRequestedSessionId();
if (requestedSessionSSL) {
// If the session ID has been obtained from the SSL handshake then
// use it.
} else if (("/".equals(context.getSessionCookiePath())
&& isRequestedSessionIdFromCookie())) {
/* This is the common(ish) use case: using the same session ID with
* multiple web applications on the same host. Typically this is
* used by Portlet implementations. It only works if sessions are
* tracked via cookies. The cookie must have a path of "/" else it
* won't be provided to for requests to all web applications.
*
* Any session ID provided by the client should be for a session
* that already exists somewhere on the host. Check if the context
* is configured for this to be confirmed.
*/
if (context.getValidateClientProvidedNewSessionId()) {
boolean found = false;
for (Container container : getHost().findChildren()) {
Manager m = ((Context) container).getManager();
if (m != null) {
try {
if (m.findSession(sessionId) != null) {
found = true;
break;
}
} catch (IOException e) {
// Ignore. Problems with this manager will be
// handled elsewhere.
}
}
}
if (!found) {
sessionId = null;
}
sessionId = getRequestedSessionId();
}
} else {
sessionId = null;
}
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è)一般是第一次訪問(wèn)的情況,即創(chuàng)建session對(duì)象,session的創(chuàng)建是調(diào)用了ManagerBase的createSession方法來(lái)實(shí)現(xiàn)的; 另外,注意response.addSessionCookieInternal方法,該方法的功能就是上面提到的往響應(yīng)頭寫(xiě)入“Set-Cookie”信息;最后,還要調(diào)用session.access方法記錄下該session的最后訪問(wèn)時(shí)間,因?yàn)閟ession是可以設(shè)置過(guò)期時(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è)一般是第二次以后訪問(wèn)的情況,通過(guò)ManagerBase的findSession方法查找session,其實(shí)就是利用map的key從ConcurrentHashMap中拿取對(duì)應(yīng)的value,這里的key即requestedSessionId,也即JSESSIONID,同時(shí)還要調(diào)用session.access方法,記錄下該session的最后訪問(wèn)時(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方法很簡(jiǎn)單,就是根據(jù)key從map中獲取value;
setAttribute方法稍微復(fù)雜點(diǎn),除了設(shè)置key-value外,如果添加了一些事件監(jiān)聽(tīng)(HttpSessionAttributeListener)的話,還要通知執(zhí)行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。
session存在的問(wèn)題
- 安全性,session劫持,這個(gè)前面已經(jīng)舉過(guò)例子了;
- 增加服務(wù)器壓力,因?yàn)閟ession是直接存儲(chǔ)在服務(wù)器的內(nèi)存中的;
- 如果存在多臺(tái)服務(wù)器的話,還存在session同步問(wèn)題,當(dāng)然如果只有一臺(tái)tomcat服務(wù)器的話,也就沒(méi)有session同步的事情了,然而現(xiàn)在一般的應(yīng)用都會(huì)用到多臺(tái)tomcat服務(wù)器,通過(guò)負(fù)載均衡,同一個(gè)會(huì)話有可能會(huì)被分配到不同的tomcat服務(wù)器,因此很可能出現(xiàn)session不一致問(wèn)題;解決session同步問(wèn)題,實(shí)際上主要是保證能夠抽離出一塊共享空間存放session信息,且這塊空間不同的tomcat服務(wù)器都可以訪問(wèn)到;一般這塊共享的空間可以是數(shù)據(jù)庫(kù),或者某臺(tái)服務(wù)器的內(nèi)存空間,甚至硬盤(pán)空間,或者客戶端的cookie也是可以的;
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Tomcat實(shí)現(xiàn)session共享(session 會(huì)話復(fù)制)
- Tomcat集群和Session復(fù)制應(yīng)用介紹
- 深入淺析TomCat Session管理分析
- nginx+tomcat實(shí)現(xiàn)負(fù)載均衡,使用redis session共享
- Tomcat中session的管理機(jī)制
- Java中tomcat memecached session 共享同步問(wèn)題的解決辦法
- 淺談Tomcat Session管理分析
- Nginx+Tomcat關(guān)于Session的管理的實(shí)現(xiàn)
- Tomcat如何監(jiān)控并刪除超時(shí)Session詳解
- Tomcat中的Session與Cookie深入講解
相關(guān)文章
使用IDEA配置tomcat及創(chuàng)建JSP文件的方法
這篇文章主要介紹了使用IDEA配置tomcat及創(chuàng)建JSP文件的方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
tomcat以及tomcat環(huán)境變量安裝配置方法教程
這篇文章主要為大家詳細(xì)介紹了tomcat以及tomcat環(huán)境變量安裝配置方法教程,感興趣的小伙伴們可以參考一下2016-06-06
解決Tomcat重新部署后圖片等資源被自動(dòng)刪除的問(wèn)題
這篇文章主要介紹了解決Tomcat重新部署后圖片等資源被自動(dòng)刪除的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
Linux下定時(shí)切割Tomcat日志并刪除指定天數(shù)前的日志記錄
這篇文章主要介紹了Linux下定時(shí)切割Tomcat日志并刪除指定天數(shù)前的日志記錄,需要的朋友可以參考下2017-08-08
SpringBoot?升級(jí)內(nèi)嵌Tomcat的操作示例
這篇文章主要介紹了SpringBoot升級(jí)內(nèi)嵌Tomcat,這里采用的是屏蔽舊的依賴,然后手動(dòng)寫(xiě)dependency的方式,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
Tomcat運(yùn)行時(shí)在IDEA控制臺(tái)輸出信息中文亂碼的解決方案
本文主要給大家介紹Tomcat運(yùn)行時(shí)在IDEA控制臺(tái)輸出信息中文亂碼的解決方案,文中的解決方案介紹的非常詳細(xì),有需要的朋友可以參考閱讀下2023-08-08
實(shí)現(xiàn)將Web應(yīng)用部署到Tomcat根目錄的三種方法
本篇文章主要介紹了實(shí)現(xiàn)將Web應(yīng)用部署到Tomcat根目錄的三種方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
解決Tomcat在IDEA控制臺(tái)亂碼問(wèn)題的詳細(xì)教程
本文詳細(xì)描述了解決Tomcat和IDEA中編碼問(wèn)題的兩種方案,包括設(shè)置JAVA_TOOL_OPTIONS,VMoptions,編輯器編碼,以及IDEA配置文件中的UTF-8編碼設(shè)置,確保重啟后問(wèn)題得到解決,需要的朋友可以參考下2024-09-09

