本文分析了rhev的 ovirt虛擬化管理平臺的登陸機制 ,如何 經過 session 實現跟蹤會話,超時登出等功能。web
連接:https://www.zhihu.com/question/19786827/answer/28752144
1. 因爲HTTP協議是無狀態的協議,因此服務端須要記錄用戶的狀態時,就須要用某種機制來識具體的用戶,這個機制就是Session.典型的場景好比購物車,當你點擊下單按鈕時,因爲HTTP協議無狀態,因此並不知道是哪一個用戶操做的,因此服務端要爲特定的用戶建立了特定的Session,用於標識這個用戶,而且跟蹤用戶,這樣才知道購物車裏面有幾本書。這個Session是保存在服務端的,有一個惟一標識。在服務端保存Session的方法不少,內存、數據庫、文件都有。集羣的時候也要考慮Session的轉移,在大型的網站,通常會有專門的Session服務器集羣,用來保存用戶會話,這個時候 Session 信息都是放在內存的,使用一些緩存服務好比Memcached之類的來放 Session。
2. 思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,須要在 Cookie 裏面記錄一個Session ID,之後每次請求把這個會話ID發送到服務器,我就知道你是誰了。有人問,若是客戶端的瀏覽器禁用了 Cookie 怎麼辦?通常這種狀況下,會使用一種叫作URL重寫的技術來進行會話跟蹤,即每次HTTP交互,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的參數,服務端據此來識別用戶。
3. Cookie其實還能夠用在一些方便用戶的場景下,設想你某次登錄過一個網站,下次登陸的時候不想再次輸入帳號了,怎麼辦?這個信息能夠寫到Cookie裏面,訪問網站的時候,網站頁面的腳本能夠讀取這個信息,就自動幫你把用戶名給填了,可以方便一下用戶。這也是Cookie名稱的由來,給用戶的一點甜頭。
因此,總結一下:
Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中;
Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。數據庫
在ovirt的 後臺保存了全部登陸的 sessionid。經過兩個map(一個new,一個old),key爲sessiondId,value爲一個map保存了當前session ,每隔指定時間,通常爲15min,會將new清空,new轉爲old,這樣一來達到更新session的機制。這同樣一個會話可能從15或者30分鐘以內。由於在更新的過程,用戶只要有新的操做,就會從old取出更新更新到new中。api
private ConcurrentMap<String, Map<String, Object>> oldContext = new ConcurrentHashMap<String, Map<String, Object>>(); private ConcurrentMap<String, Map<String, Object>> newContext = new ConcurrentHashMap<String, Map<String, Object>>();
session 的生成瀏覽器
通常由web服務器成一串隨機的字符串,如在servlet中,緩存
HttpServletRequest request = this.getThreadLocalRequest(); HttpSession session = request.getSession();
也可由服務器後臺生成,在ovrit中的api調用中,session是在.而ovirt中的restApi是resteasy的框架,能夠定義 攔截器(實現 PreProcessInterceptor 接口),在每次調用處理前,調用Interceptor的prepocess方法。如在ovirt中,就定義一個Challenger 用來驗證session。服務器
@Provider @ServerInterceptor @Precedence("SECURITY") public class Challenger implements PreProcessInterceptor { ..... public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { ...... } }
而在Challenger的preProcess 的方法中,就能夠用來完成login或者session的相關處理。cookie
//Challenger api驗證,生成sessionId String engineSessionId = SessionUtils.generateEngineSessionId();
而在ovirt中session
由前臺經過 servlet 調用後臺 的 query仍是command的調用數據結構
都會將sessionid 傳遞,在後臺 GenericApiGWTServiceImpl ( 其繼承了RPCServlet)收到前臺的RPC調用後(查詢或者執行後臺),框架
//執行command @Override public VdcReturnValueBase RunAction(VdcActionType actionType, VdcActionParametersBase params) { log.debug("Server: RunAction invoked!"); //$NON-NLS-1$ debugAction(actionType, params); params.setSessionId(getSession().getId()); // SessionID的傳遞 if (noBackend) { VdcReturnValueBase rValue = new VdcReturnValueBase(); rValue.setSucceeded(true); return rValue; } return getBackend().runAction(actionType, params); }
而在執行query或者執行Command前,都會去驗證session,以下是 查詢,判斷當前session是否有有效,當session失敗時,返回Error。
protected VdcQueryReturnValue runQueryImpl(VdcQueryType actionType, VdcQueryParametersBase parameters, boolean isPerformUserCheck) { if (isPerformUserCheck) { String sessionId = addSessionToContext(parameters); if (StringUtils.isEmpty(sessionId) || SessionDataContainer.getInstance().getUser(sessionId, parameters.getRefresh()) == null) { return getErrorQueryReturnValue(VdcBllMessages.USER_IS_NOT_LOGGED_IN); } }
這樣一來,瀏覽器中保存sessionID的值 ,並傳遞到後,後臺驗證session是否有效。以此完成session的會話機制。超時退出等功能。
而對通常的調用過程當中,用戶觸發的邏輯須要更新session,而前臺的定時刷新等query就不須要刷新session。
在以前的版本中,無法精確的實現超時退出,超時的時間在所設置的時間兩倍之內。要實現精確的時間設置,就須要設置一個屬性,記錄最後一次刷新的時間。咱們看看4.0+是如何實現的。
只經過一個Map保存session信息,key爲sessionid,而這裏的value爲 封裝了一個map的 session類,而這個sessiono類中,保存了map的信息,包括以下:
private static final String USER_PARAMETER_NAME = "user"; private static final String SOURCE_IP = "source_ip"; private static final String PROFILE_PARAMETER_NAME = "profile"; private static final String HARD_LIMIT_PARAMETER_NAME = "hard_limit"; private static final String SOFT_LIMIT_PARAMETER_NAME = "soft_limit"; private static final String ENGINE_SESSION_SEQ_ID = "engine_session_seq_id"; private static final String ENGINE_SESSION_ID = "engine_session_id"; private static final String PRINCIPAL_PARAMETER_NAME = "username"; private static final String SSO_ACCESS_TOKEN_PARAMETER_NAME = "sso_access_token"; private static final String SESSION_VALID_PARAMETER_NAME = "session_valid"; private static final String SOFT_LIMIT_INTERVAL_PARAMETER_NAME = "soft_limit_interval"; private static final String SESSION_START_TIME = "session_start_time"; private static final String SESSION_LAST_ACTIVE_TIME = "session_last_active_time";
其中softLimit 對應的value爲最後一次刷新的當前時間加上所設置的時間間隔。而同時增長了一個計劃任務,每隔一分鐘就檢測一次,根據softLimit的大小是否小於當前時間,不然刪除。