微服務(wù)session落坑記.jpg
本文適用于對session、cookie有一定了解的同學(xué)驾霜,主要以問題定位過程為線索案训,簡單講述tomcat session生成機(jī)制、oauth2認(rèn)證過程以及spring方法參數(shù)映射處理等內(nèi)容
背景知識:
- session:由于http協(xié)議無狀態(tài)粪糙,為了保存用戶狀態(tài)信息强霎,web容器支持session管理機(jī)制,當(dāng)客戶端請求web應(yīng)用時(shí)蓉冈,如果在處理過程中調(diào)用了request.getSession()方法城舞,則web容器會先根據(jù)url或者cookie中上傳的JSESSIONID(默認(rèn),可以另行設(shè)置寞酿,該值會被設(shè)置到request對象的requestedSessionId字段)查找對應(yīng)session家夺,如果獲取不到將自動創(chuàng)建一個(gè)session對象;當(dāng)session過期或被放棄后伐弹,服務(wù)器將終止該session
protected Session doGetSession(boolean create) {
// 略去部分代碼
(requestedSessionId != null) {
session = manager.findSession(requestedSessionId);
// 略去部分代碼
String sessionId = getRequestedSessionId();
// 略去部分代碼
session = manager.createSession(sessionId);
// 創(chuàng)建cookie并寫入response
if (session != null
&& context.getServletContext()
.getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.COOKIE)) {
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
// 略去部分代碼
}
- cookie:為了服務(wù)器能夠識別不同的客戶端拉馋,客戶端通過cookie保存服務(wù)端返回的數(shù)據(jù)(如JSESSIONID,tomcat服務(wù)器默認(rèn)的session對應(yīng)的cookie key為JSESSIONID)惨好,然后服務(wù)端通過客戶端請求時(shí)放在http header中的cookie數(shù)據(jù)找到之前為客戶端創(chuàng)建的session煌茴;cookie分為會話cookie和持久cookie,會話Cookie瀏覽器會話有效期間存在日川,持久cookie服務(wù)端會設(shè)置http header 緩存相關(guān)字段指示客戶端緩存策略景馁,cookie傳到客戶端后,保存在某個(gè)目錄下
- oauth2:oauth是一個(gè)開放標(biāo)準(zhǔn)逗鸣,允許用戶讓第三方應(yīng)用訪問該用戶在某一網(wǎng)站上存儲的私密的資源(如照片合住,視頻,聯(lián)系人列表)撒璧,而無需將用戶名和密碼提供給第三方應(yīng)用透葛,可以用于微服務(wù)環(huán)境下的公共鑒權(quán),而本文服務(wù)鑒權(quán)走的就是oauth2
前人挖坑后人落卿樱,都是框架惹的禍
問題特征:
- 需求上線在即僚害,發(fā)現(xiàn)某個(gè)接口請求后會新生成JSESSIONID并以Cookie形式回寫瀏覽器,導(dǎo)致后續(xù)請求鑒權(quán)失敗繁调,必現(xiàn)
- 服務(wù)相互調(diào)用關(guān)系比較復(fù)雜萨蚕,需要在開發(fā)環(huán)境測試該功能,本地沒有測試
問題定位過程:
- 交流得知蹄胰,此前定位過程中岳遥,將被請求方法體代碼全部注銷后,問題仍然存在裕寨,于是錯過了查看問題代碼(其實(shí)是方法參數(shù)出了問題)浩蓉,盡早定位出問題的好機(jī)會
- 懷疑是否nginx會話保持策略導(dǎo)致的問題(但是該策略應(yīng)該會對所有請求均產(chǎn)生影響派继,而不只是單個(gè)請求),查看nginx.conf配置捻艳,該服務(wù)沒有走負(fù)載均衡
- 懷疑是否nginx對該請求路徑(路徑中包含auth敏感字段)做了特殊配置驾窟,但查看配置,nginx對于該服務(wù)請求路徑配置規(guī)則很簡單认轨,且將請求路徑中auth字段刪掉后測試绅络,問題仍然存在
- 懷疑是否有外圍代碼重寫了cookie,搜索沒找到相關(guān)代碼
- 梳理服務(wù)鑒權(quán)過程(見top圖嘁字,圖中做了縮略昨稼,將公共認(rèn)證服務(wù)刪除,直接對接oauth2)拳锚,描述如下:
1)認(rèn)證鑒權(quán)邏輯被封裝在了公共Filter中,對所有請求進(jìn)行攔截寻行,判斷是否已登錄
2)如果沒有上傳JSESSIONID或者無法據(jù)其在redis找到session及token信息霍掺,返回客戶端重定向到login接口
3)客戶端調(diào)用login接口:服務(wù)端調(diào)用oauth2(其實(shí)有個(gè)公共的鑒權(quán)服務(wù))認(rèn)證合法性,將認(rèn)證返回的token信息融合自身的JSESSIONID寫入redis拌蜘,隨后將服務(wù)端JSESSIONID寫回客戶端cookie杆烁,坑:此處復(fù)用了tomcat默認(rèn)的JSESSIONID,推測是為了復(fù)用request的getRequestedSessionId方法简卧,該方法會直接取客戶端cookie提交的JSESSIONID兔魂,業(yè)務(wù)中多次用到了getRequestedSessionId方法,tomcat session和服務(wù)認(rèn)證返回session除了都叫JSESSIONID外举娩,沒有半毛錢關(guān)系析校,但是因?yàn)橹孛嗷ブg會覆寫
4)客戶端帶著認(rèn)證返回的JSESSIONID繼續(xù)調(diào)用之前的業(yè)務(wù)接口铜涉,通過認(rèn)證智玻,走業(yè)務(wù)邏輯,其實(shí)此時(shí)tomcat中的JSESSIONID和上傳的JSESSIONID不一致芙代,根據(jù)上傳的JSESSIONID在tomcat是找不到對應(yīng)session的
5)認(rèn)證過程還有其他邏輯吊奢,此處做了刪減,不影響主體流程 - 梳理之后纹烹,發(fā)現(xiàn)二者之間雖然key一致但是值不同页滚,而且彼此會干擾,于是懷疑外圍代碼調(diào)用了getSession方法铺呵,搜索代碼未找到
- 查看請求路徑對應(yīng)方法裹驰,方法中使用了session參數(shù),但是方法體中未使用該參數(shù)片挂,聯(lián)想到spring的參數(shù)值自動映射機(jī)制(見ServletRequestMethodArgumentResolver)邦马,在為session賦值的過程中調(diào)用了getSession方法,進(jìn)而由于在tomcat找不到對應(yīng)session而新建、回寫相關(guān)cookie到客戶端
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// 略去部分代碼
if (HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
// 略去部分代碼
}
}
- 刪除參數(shù)滋将,測試邻悬,問題解決
坑:
- 使用該認(rèn)證機(jī)制的服務(wù)的業(yè)務(wù)方法中不要使用tomcat的session機(jī)制(如getSession或session參數(shù)),否則就會出現(xiàn)這個(gè)問題
解決方案:
- 針對此問題随闽,將session參數(shù)去掉
- 將認(rèn)證機(jī)制的JSESSIONID換成另外一個(gè)名字父丰,但是改動量比較大,可能會有其他坑
- 走分布式session解決方案掘宪,將tomcat的session管理機(jī)制和分布式session結(jié)合起來
- 走jwt token處理機(jī)制蛾扇,服務(wù)端不再存儲token,僅做鑒權(quán)和刷新
歡迎關(guān)注我的微信公眾號
68號小喇叭