現在遇到一個需求就是要求完成簡單的單點登入,通過在一個tomcat執行個體中放置兩個webapps應用ROOT應用和CEO應用來完成在ROOT應用登入後,在CEO可以直接使用,而未在ROOT應用登入時,不可以進去CEO應用。
實際上問題就是session如何在兩個webapp中實作共享,通過上網搜尋發現一個方法
方法1ServletContext
server.xml檔案修改如下:
<Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="true">
//WebappA為項目名,crossContext="true"
<Context path="/WebappA" debug="9" reloadable="true" crossContext="true"/>
<Context path="/WebappB" debug="9" reloadable="true" crossContext="true"/>
</Host>
crossContext屬性的意思是:如果設定為true,你可以通過ServletContext.getContext() 調用另外一個WEB應用程式,獲得ServletContext 然後再調用其getAttribute() 得到你要的對象。
Java代碼如下:
WebappA:
HttpSession session = request.getSession();
session.setAttribute("userId", "test");
ServletContext ContextA =session .getServletContext();
ContextA.setAttribute("session", session );
WebappB:
HttpSession sessionB = request.getSession();
ServletContext ContextB = sessionB.getServletContext();
ServletContext ContextA= ContextB.getContext("/WebappA");// 這裡面傳遞的是 WebappA的虛拟路徑
HttpSession sessionA =(HttpSession)ContextA.getAttribute("session");
System.out.println("userId: "+sessionA.getAttribute("userId"));
初看這個方法,好像是完成我們的目标,可是在我實際應用時發現一個問題,就是當user1在登入前不可以進入CEO應用,在user1登入後才可以進入CEO應用,但是當user1退出之後,未登入的使用者依然可以進入CEO應用。
後來仔細看了一下網上提供的方法,它隻是在webappA的ServletContext存儲了一個session值,然後傳遞給webAPPB,但是也僅僅隻能傳遞一個session值,如果有兩個使用者的時候就會出現session覆寫。
于是探究其他解決方法。
方法2sessionCookiePath
在tomcat conf/context.html中有如下配置
-
sessionCookieName
The name to be used for all session cookies created for this context. If set, this overrides any name set by the web application. If not set, the value specified by the web application, if any, will be used, or the name JSESSIONID if the web application does not explicitly set one.
-
sessionCookiePath
The path to be used for all session cookies created for this context. If set, this overrides any path set by the web application. If not set, the value specified by the web application will be used, or the context path used if the web application does not explicitly set one. To configure all web application to use an empty path (this can be useful for portlet specification implementations) set this attribute to / in the global CATALINA_BASE/conf/context.xml file.
Note: Once one web application using sessionCookiePath="/" obtains a session, all subsequent sessions for any other web application in the same host also configured with sessionCookiePath="/" will always use the same session ID. This holds even if the session is invalidated and a new one created. This makes session fixation protection more difficult and requires custom, Tomcat specific code to change the session ID shared by the multiple applications.
也就是說我們可以通過
sessionCookiePath
屬性使得一個tomcat執行個體下所有的webapps都共享一個session,通過sessionCookieName來指定sessionCookieName名字。
于是我就在tomcat conf/context.html中配置如下:
<Context sessionCookiePath="/" sessionCookieName="SESSIONID" >
</Context>
然後在進行測試,發現在ROOT應用和CEO應用中确實sessionCookie是一樣的。可是當我在ROOT中進行
session.setAttribute();
時,CEO應用不能從session中取得值,為null,也就是說,對CEO應用而言,ROOT應用在session所儲存的值是不可見的。然後在CEO session中進行
session.setAttribute();
,ROOT應用總同樣無法取得CEO存儲在session中的資料,猜想可能是不同的webapps并不會共享相同的session記憶體,每一個webapps維護自己session HashTable,後來了解到 session管理器是和context容器關聯的,也就說每個web應用都會有一個session管理器,是以CEO應用當然無法從ROOT應用存儲的session中取值。
方法3redis session 共享
以前曾經了解過Nginx+tomcat+redis做負載均衡的内容,知道可以把session資料存儲到redis中,然後tomcat再去redis取值。
而這次的tomcat中session在兩個webapp中實作共享其實也可以通過這個方法進行處理。
在tomcat conf/context.html中配置如下:
<Context sessionCookiePath="/" sessionCookieName="SESSIONID" >
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="172.22.4.16"
port="6379"
database="0"
maxInactiveInterval="60" />
</Context>
然後即可實作tomcat中session在兩個webapp中實作共享。
jar包和windows redis安裝包
http://pan.baidu.com/s/1eSITNnc
session過期政策
tomcat-session怎麼實作的過期政策?
首先如果沒有使用redis做session緩存,tomcat伺服器在啟動的時候初始化了一個守護線程,定期6*10秒去檢查有沒有Session過期.過期則清除。而使用的tomcat-session-redis做緩存,那麼session過期之後就由redis進行删除,redis通過惰性删除和定期删除來删除過期的sessionID值。
當然sessionID還存在于用戶端,那麼用戶端的sessionID清理過程是什麼?
經過測試是當tomcat删除sessionID值之後,tomcat會重新生成一個sessionID值傳回給用戶端。

總結:
其實我這個功能就是單點登入,也就是說在A應用登入的情況下可以通路B應用,但是即使設定了
sessionCookiePath
,session的Attribute并沒有共享,于是想到了先把session序列化到redis中,然後取出來判斷,這樣就可以實作單點登入。核心是session在這兩個應用中必須是一樣的,通過設定
sessionCookiePath
。
注意:
- 當你使用自己的對象執行
時,必須實作session.setAttribute();
接口,不然無法進行序列化。Serializable
-
maxInactiveInterval
不起作用
tomcat日志描述:
警告: Manager.setMaxInactiveInterval() is deprecated and calls to this method are ignored. Session timeouts should be configured in web.xml or via Context.setSessionTimeout(int timeoutInMinutes).
資訊: Will expire sessions after 120 seconds(預設是30分鐘)
隻能通過在ROOT下的web.xml或者全局的web.xml(CEO中的session-config無法生效)中配置才可生效。
<!--設定session過期時間--> <session-config> <session-timeout>20</session-timeout> </session-config>
配置成功後tomcat日志:
資訊: Will expire sessions after 120 seconds
- 在context中配置host為靜态ip 172.22.4.16報錯如下: ,Google發現原來redis和mysql一樣都是已經預設綁定了localhost,隻允許本機通路,于是更改
tomcat中session在兩個webapp中實作共享
如下redis.windows-service.conf
之後就可以使用靜态ip 172.22.4.16進行通路。bind 127.0.0.1 172.22.4.16
- msi應用的安裝,修複和解除安裝都是通過點選msi檔案。
- 還有一個共享session的方案是spring-session。 spring-session中通過自己生成session并且存儲到redis中,還是需要設定
(在一個tomcat兩個應用需要單點登入的情況),其他session共享方案(在多個tomcat中實作單點登入可以參考 spring session無法實作共享(多web應用)),但是spring-session不是伺服器級别的,而是web 應用級别的,不受伺服器如tomcat,jetty,jboss的限制。sessionCookiePath="/"
參考文檔
- http://tomcat.apache.org/tomcat-7.0-doc/config/context.html
- 搭建Tomcat叢集&通過Redis緩存共享session的一種流行方案
- Tomcat的Session過期處理政策
- Tomcat中session的管理機制