SpringSession系列-sessionId解析和Cookie讀寫(xiě)策略

sessionId 解析策略

SpringSession中對(duì)于sessionId的解析相關(guān)的策略是通過(guò)HttpSessionIdResolver這個(gè)接口來(lái)體現(xiàn)的。HttpSessionIdResolver有兩個(gè)實(shí)現(xiàn)類(lèi):

這兩個(gè)類(lèi)就分別對(duì)應(yīng)SpringSession解析sessionId的兩種不同的實(shí)現(xiàn)策略流强。再深入了解不同策略的實(shí)現(xiàn)細(xì)節(jié)之前痹届,先來(lái)看下HttpSessionIdResolver接口定義的一些行為有哪些。

HttpSessionIdResolver

HttpSessionIdResolver定義了sessionId解析策略的契約(Contract)打月。允許通過(guò)請(qǐng)求解析sessionId队腐,并通過(guò)響應(yīng)發(fā)送sessionId或終止會(huì)話(huà)。接口定義如下:

publicinterfaceHttpSessionIdResolver{ListresolveSessionIds(HttpServletRequest request);voidsetSessionId(HttpServletRequest request, HttpServletResponse response,String sessionId);voidexpireSession(HttpServletRequest request, HttpServletResponse response);}

HttpSessionIdResolver中有三個(gè)方法:

resolveSessionIds:解析與當(dāng)前請(qǐng)求相關(guān)聯(lián)的sessionId奏篙。sessionId可能來(lái)自Cookie或請(qǐng)求頭柴淘。

setSessionId:將給定的sessionId發(fā)送給客戶(hù)端。這個(gè)方法是在創(chuàng)建一個(gè)新session時(shí)被調(diào)用,并告知客戶(hù)端新sessionId是什么悠就。

expireSession:指示客戶(hù)端結(jié)束當(dāng)前session千绪。當(dāng)session無(wú)效時(shí)調(diào)用此方法充易,并應(yīng)通知客戶(hù)端sessionId不再有效梗脾。比如,它可能刪除一個(gè)包含sessionId的Cookie盹靴,或者設(shè)置一個(gè)HTTP響應(yīng)頭炸茧,其值為空就表示客戶(hù)端不再提交sessionId。

下面就針對(duì)上面提到的兩種策略來(lái)進(jìn)行詳細(xì)的分析稿静。

基于Cookie解析sessionId

這種策略對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)是CookieHttpSessionIdResolver梭冠,通過(guò)從Cookie中獲取session改备;具體來(lái)說(shuō),這個(gè)實(shí)現(xiàn)將允許使用CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)指定Cookie序列化策略悬钳。默認(rèn)的Cookie名稱(chēng)是“SESSION”。創(chuàng)建一個(gè)session時(shí)默勾,HTTP響應(yīng)中將會(huì)攜帶一個(gè)指定Cookie name且value是sessionId的Cookie。Cookie將被標(biāo)記為一個(gè)session cookie母剥,Cookie的domain path使用context path,且被標(biāo)記為HttpOnly环疼,如果HttpServletRequest#isSecure()返回true,那么Cookie將標(biāo)記為安全的炫隶。如下:

HTTP/1.1 200 OKSet-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly

這個(gè)時(shí)候,客戶(hù)端應(yīng)該通過(guò)在每個(gè)請(qǐng)求中指定相同的Cookie來(lái)包含session信息等限。例如:

GET /messages/ HTTP/1.1 Host: example.com Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6

當(dāng)會(huì)話(huà)無(wú)效時(shí)爸吮,服務(wù)器將發(fā)送過(guò)期的HTTP響應(yīng)Cookie,例如:

HTTP/1.1 200 OK Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly

CookieHttpSessionIdResolver類(lèi)的實(shí)現(xiàn)如下:

publicfinalclassCookieHttpSessionIdResolverimplementsHttpSessionIdResolver{privatestaticfinalString WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class.getName().concat(".WRITTEN_SESSION_ID_ATTR");// Cookie序列化策略望门,默認(rèn)是 DefaultCookieSerializerprivateCookieSerializer cookieSerializer =newDefaultCookieSerializer();@OverridepublicListresolveSessionIds(HttpServletRequest request){// 根據(jù)提供的cookieSerializer從請(qǐng)求中獲取sessionIdreturnthis.cookieSerializer.readCookieValues(request);}@OverridepublicvoidsetSessionId(HttpServletRequest request, HttpServletResponse response,

String sessionId){if(sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {return;}request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);// 根據(jù)提供的cookieSerializer將sessionId回寫(xiě)到cookie中this.cookieSerializer.writeCookieValue(newCookieValue(request, response, sessionId));}@OverridepublicvoidexpireSession(HttpServletRequest request, HttpServletResponse response){// 這里因?yàn)槭沁^(guò)期形娇,所以回寫(xiě)的sessionId的值是“”,當(dāng)請(qǐng)求下次進(jìn)來(lái)時(shí)筹误,就會(huì)取不到sessionId桐早,也就意味著當(dāng)前會(huì)話(huà)失效了this.cookieSerializer.writeCookieValue(newCookieValue(request, response,""));}// 指定Cookie序列化的方式publicvoidsetCookieSerializer(CookieSerializer cookieSerializer){if(cookieSerializer ==null) {thrownewIllegalArgumentException("cookieSerializer cannot be null");}this.cookieSerializer = cookieSerializer;}}

這里可以看到CookieHttpSessionIdResolver中的讀取操作都是圍繞CookieSerializer來(lái)完成的。CookieSerializer是SpringSession中對(duì)于Cookie操作提供的一種機(jī)制。下面細(xì)說(shuō)哄酝。

基于請(qǐng)求頭解析sessionId

這種策略對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)是HeaderHttpSessionIdResolver友存,通過(guò)從請(qǐng)求頭header中解析出sessionId。具體地說(shuō)陶衅,這個(gè)實(shí)現(xiàn)將允許使用HeaderHttpSessionIdResolver(String)來(lái)指定頭名稱(chēng)屡立。還可以使用便利的工廠(chǎng)方法來(lái)創(chuàng)建使用公共頭名稱(chēng)(例如“X-Auth-Token”和“authenticing-info”)的實(shí)例。創(chuàng)建會(huì)話(huà)時(shí)搀军,HTTP響應(yīng)將具有指定名稱(chēng)和sessionId值的響應(yīng)頭膨俐。

// 使用X-Auth-Token作為headerNamepublicstaticHeaderHttpSessionIdResolverxAuthToken(){returnnewHeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);}// 使用Authentication-Info作為headerNamepublicstaticHeaderHttpSessionIdResolverauthenticationInfo(){returnnewHeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);}

HeaderHttpSessionIdResolver在處理sessionId上相比較于CookieHttpSessionIdResolver來(lái)說(shuō)簡(jiǎn)單很多。就是圍繞request.getHeader(String)和request.setHeader(String,String)兩個(gè)方法來(lái)玩的罩句。

HeaderHttpSessionIdResolver這種策略通常會(huì)在無(wú)線(xiàn)端來(lái)使用焚刺,以彌補(bǔ)對(duì)于無(wú)Cookie場(chǎng)景的支持。

Cookie 序列化策略

基于Cookie解析sessionId的實(shí)現(xiàn)類(lèi)CookieHttpSessionIdResolver中實(shí)際對(duì)于Cookie的讀寫(xiě)操作都是通過(guò)CookieSerializer來(lái)完成的门烂。SpringSession提供了CookieSerializer接口的默認(rèn)實(shí)現(xiàn)DefaultCookieSerializer乳愉,當(dāng)然在實(shí)際應(yīng)用中,我們也可以自己實(shí)現(xiàn)這個(gè)接口屯远,然后通過(guò)CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)方法來(lái)指定我們自己的實(shí)現(xiàn)方式蔓姚。

PS:不得不說(shuō),強(qiáng)大的用戶(hù)擴(kuò)展能力真的是Spring家族的優(yōu)良家風(fēng)氓润。

jvm_router的處理

CookieValue

CookieValue是CookieSerializer中的內(nèi)部類(lèi)赂乐,封裝了向HttpServletResponse寫(xiě)入所需的所有信息。其實(shí)CookieValue的存在并沒(méi)有什么特殊的意義咖气,個(gè)人覺(jué)得作者一開(kāi)始只是想通過(guò)CookieValue的封裝來(lái)簡(jiǎn)化回寫(xiě)cookie鏈路中的參數(shù)傳遞的問(wèn)題挨措,但是實(shí)際上貌似并沒(méi)有什么減少多少工作量。

Cookie 回寫(xiě)

Cookie回寫(xiě)我覺(jué)得對(duì)于分布式session的實(shí)現(xiàn)來(lái)說(shuō)是必不可少的崩溪;基于標(biāo)準(zhǔn)servlet實(shí)現(xiàn)的HttpSession浅役,我們?cè)谑褂脮r(shí)實(shí)際上是不用關(guān)心回寫(xiě)cookie這個(gè)事情的,因?yàn)閟ervlet容器都已經(jīng)做了伶唯。但是對(duì)于分布式session來(lái)說(shuō)觉既,由于重寫(xiě)了response,所以需要在返回response時(shí)需要將當(dāng)前session信息通過(guò)cookie的方式塞到response中返回給客戶(hù)端-這就是Cookie回寫(xiě)乳幸。下面是DefaultCookieSerializer中回寫(xiě)Cookie的邏輯粹断,細(xì)節(jié)在代碼中通過(guò)注釋標(biāo)注出來(lái)。

@OverridepublicvoidwriteCookieValue(CookieValue cookieValue){HttpServletRequest request = cookieValue.getRequest();HttpServletResponse response = cookieValue.getResponse();StringBuilder sb =newStringBuilder();sb.append(this.cookieName).append('=');String value = getValue(cookieValue);if(value !=null&& value.length() >0) {validateValue(value);sb.append(value);}intmaxAge = getMaxAge(cookieValue);if(maxAge > -1) {sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());OffsetDateTime expires = (maxAge !=0)? OffsetDateTime.now().plusSeconds(maxAge): Instant.EPOCH.atOffset(ZoneOffset.UTC);sb.append("; Expires=").append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));}String domain = getDomainName(request);if(domain !=null&& domain.length() >0) {validateDomain(domain);sb.append("; Domain=").append(domain);}String path = getCookiePath(request);if(path !=null&& path.length() >0) {validatePath(path);sb.append("; Path=").append(path);}if(isSecureCookie(request)) {sb.append("; Secure");}if(this.useHttpOnlyCookie) {sb.append("; HttpOnly");}if(this.sameSite !=null) {sb.append("; SameSite=").append(this.sameSite);}response.addHeader("Set-Cookie", sb.toString());}

這上面就是拼湊字符串希柿,然后塞到Header里面去,最終再瀏覽器中顯示大體如下:

Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly

jvm_router的處理

在Cookie的讀寫(xiě)代碼中都涉及到對(duì)于jvmRoute這個(gè)屬性的判斷及對(duì)應(yīng)的處理邏輯端姚。

1渐裸、讀取Cookie中的代碼片段

if(this.jvmRoute !=null&& sessionId.endsWith(this.jvmRoute)) {sessionId = sessionId.substring(0,sessionId.length() -this.jvmRoute.length());}

2尖啡、回寫(xiě)Cookie中的代碼片段

if(this.jvmRoute !=null) {actualCookieValue = requestedCookieValue +this.jvmRoute;}

jvm_route是Nginx中的一個(gè)模塊剩膘,其作用是通過(guò)session cookie的方式來(lái)獲取session粘性怠褐。如果在cookie和url中并沒(méi)有session,則這只是個(gè)簡(jiǎn)單的round-robin負(fù)載均衡奠涌。其具體過(guò)程分為以下幾步:

1.第一個(gè)請(qǐng)求過(guò)來(lái)磷杏,沒(méi)有帶session信息极祸,jvm_route就根據(jù)round robin策略發(fā)到一臺(tái)tomcat上面。

2.tomcat添加上session信息浴捆,并返回給客戶(hù)稿械。

3.用戶(hù)再次請(qǐng)求,jvm_route看到session中有后端服務(wù)器的名稱(chēng)页眯,它就把請(qǐng)求轉(zhuǎn)到對(duì)應(yīng)的服務(wù)器上窝撵。

從本質(zhì)上來(lái)說(shuō)述吸,jvm_route也是解決session共享的一種解決方式锣笨。這種和基于IP-HASH的方式有點(diǎn)類(lèi)似错英。那么同樣椭岩,這里存在的問(wèn)題是無(wú)法解決宕機(jī)后session數(shù)據(jù)轉(zhuǎn)移的問(wèn)題璃赡,既宕機(jī)就丟失。

DefaultCookieSerializer中除了Cookie的讀寫(xiě)之后塌计,還有一些細(xì)節(jié)也值得關(guān)注下侯谁,比如對(duì)Cookie中值的驗(yàn)證墙贱、remember-me的實(shí)現(xiàn)等。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群伊脓。交流學(xué)習(xí)群號(hào):938837867 暗號(hào):555 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring魁衙,MyBatis纺棺,Netty源碼分析,高并發(fā)茅撞、高性能巨朦、分布式、微服務(wù)架構(gòu)的原理拄查,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奸攻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子糊探,更是在濱河造成了極大的恐慌,老刑警劉巖褥紫,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓考,死亡現(xiàn)場(chǎng)離奇詭異绳军,居然都是意外死亡矢腻,警方通過(guò)查閱死者的電腦和手機(jī)射赛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)楣责,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人初嘹,你說(shuō)我怎么就攤上這事屯烦》棵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵翁狐,是天一觀(guān)的道長(zhǎng)露懒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)懈词,這世上最難降的妖魔是什么钦睡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任荞怒,我火速辦了婚禮,結(jié)果婚禮上衰抑,老公的妹妹穿的比我還像新娘荧嵌。我一直安慰自己,他們只是感情好谭网,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布愉择。 她就那樣靜靜地躺著织中,像睡著了一般。 火紅的嫁衣襯著肌膚如雪层坠。 梳的紋絲不亂的頭發(fā)上破花,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天磅氨,我揣著相機(jī)與錄音,去河邊找鬼延赌。 笑死挫以,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播大磺,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼待榔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锐锣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稽荧,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎擅腰,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體歼争,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沐绒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年乔遮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了取刃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片出刷。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馁龟,死狀恐怖坷檩,靈堂內(nèi)的尸體忽然破棺而出改抡,到底是詐尸還是另有隱情,我是刑警寧澤裸删,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布涯塔,位于F島的核電站清蚀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏榛搔。R本人自食惡果不足惜东揣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一嘶卧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芥吟,春花似錦、人聲如沸钉稍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)羞秤。三九已至,卻和暖如春俐镐,著一層夾襖步出監(jiān)牢的瞬間哺哼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工棍苹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茵汰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓栏豺,卻偏偏與公主長(zhǎng)得像豆胸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灵奖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 會(huì)話(huà)(Session)跟蹤是Web程序中常用的技術(shù)瓷患,用來(lái)跟蹤用戶(hù)的整個(gè)會(huì)話(huà)忿檩。常用的會(huì)話(huà)跟蹤技術(shù)是Cookie與Se...
    chinariver閱讀 5,620評(píng)論 1 49
  • 目錄Cookie機(jī)制什么是CookieCookie的不可跨域名性Unicode編碼:保存中文BASE64編碼:保存...
    Tomatoro閱讀 16,946評(píng)論 7 186
  • 背景在HTTP協(xié)議的定義中燥透,采用了一種機(jī)制來(lái)記錄客戶(hù)端和服務(wù)器端交互的信息班套,這種機(jī)制被稱(chēng)為cookie故河,cooki...
    時(shí)芥藍(lán)閱讀 2,366評(píng)論 1 17
  • 1、不安全的隨機(jī)數(shù)生成理盆,在CSRF TOKEN生成、password reset token生成等衷快,會(huì)造成toke...
    nightmare丿閱讀 3,698評(píng)論 0 1
  • 每個(gè)人每天都會(huì)遇到人際關(guān)系問(wèn)題。 01 一位兒媳婦抱怨:“我的婆婆太讓我無(wú)法接受了调窍,她平時(shí)不愿意幫我們帶孩子邓萨,偶爾...
    月亮小姐6閱讀 2,337評(píng)論 4 26