微服務session落坑記

本文適用於對session、cookie有必定了解的同窗,主要以問題定位過程爲線索,簡單講述tomcat session生成機制、oauth2認證過程以及spring方法參數映射處理等內容nginx

背景知識web

  • session:因爲http協議無狀態,爲了保存用戶狀態信息,web容器支持session管理機制,當客戶端請求web應用時,若是在處理過程當中調用了request.getSession()方法,則web容器會先根據url或者cookie中上傳的JSESSIONID(默認,能夠另行設置,該值會被設置到request對象的requestedSessionId字段)查找對應session,若是獲取不到將自動建立一個session對象;當session過時或被放棄後,服務器將終止該session
protected Session doGetSession(boolean create) {
    // 略去部分代碼
    if (requestedSessionId != null) {
        session = manager.findSession(requestedSessionId);
        // 略去部分代碼
        String sessionId = getRequestedSessionId();
        // 略去部分代碼
        session = manager.createSession(sessionId);

        // 建立cookie並寫入response
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());
            response.addSessionCookieInternal(cookie);
        }
        // 略去部分代碼
    }
複製代碼
  • cookie:爲了服務器可以識別不一樣的客戶端,客戶端經過cookie保存服務端返回的數據(如JSESSIONID,tomcat服務器默認的session對應的cookie key爲JSESSIONID),而後服務端經過客戶端請求時放在http header中的cookie數據找到以前爲客戶端建立的session;cookie分爲會話cookie和持久cookie,會話Cookie瀏覽器會話有效期間存在,持久cookie服務端會設置http header 緩存相關字段指示客戶端緩存策略,cookie傳到客戶端後,保存在某個目錄下
  • oauth2:oauth是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用,能夠用於微服務環境下的公共鑑權,而本文服務鑑權走的就是oauth2

前人挖坑後人落,都是框架惹的禍

問題特徵redis

  • 需求上線在即,發現某個接口請求後會新生成JSESSIONID並以Cookie形式回寫瀏覽器,致使後續請求鑑權失敗,必現
  • 服務相互調用關係比較複雜,須要在開發環境測試該功能,本地沒有測試

問題定位過程:spring

  • 交流得知,此前定位過程當中,將被請求方法體代碼所有註銷後,問題仍然存在,因而錯過了查看問題代碼(實際上是方法參數出了問題),儘早定位出問題的好機會
  • 懷疑是否nginx會話保持策略致使的問題(可是該策略應該會對全部請求均產生影響,而不僅是單個請求),查看nginx.conf配置,該服務沒有走負載均衡
  • 懷疑是否nginx對該請求路徑(路徑中包含auth敏感字段)作了特殊配置,但查看配置,nginx對於該服務請求路徑配置規則很簡單,且將請求路徑中auth字段刪掉後測試,問題仍然存在
  • 懷疑是否有外圍代碼重寫了cookie,搜索沒找到相關代碼
  • 梳理服務鑑權過程(見top圖,圖中作了縮略,將公共認證服務刪除,直接對接oauth2),描述以下:
    1)認證鑑權邏輯被封裝在了公共Filter中,對全部請求進行攔截,判斷是否已登陸
    2)若是沒有上傳JSESSIONID或者沒法據其在redis找到session及token信息,返回客戶端重定向到login接口
    3)客戶端調用login接口:服務端調用oauth2(其實有個公共的鑑權服務)認證合法性,將認證返回的token信息融合自身的JSESSIONID寫入redis,隨後將服務端JSESSIONID寫回客戶端cookie,坑:此處複用了tomcat默認的JSESSIONID,推測是爲了複用request的getRequestedSessionId方法,該方法會直接取客戶端cookie提交的JSESSIONID,業務中屢次用到了getRequestedSessionId方法,tomcat session和服務認證返回session除了都叫JSESSIONID外,沒有半毛錢關係,可是由於重名,相互之間會覆寫
    4)客戶端帶着認證返回的JSESSIONID繼續調用以前的業務接口,經過認證,走業務邏輯,其實此時tomcat中的JSESSIONID和上傳的JSESSIONID不一致,根據上傳的JSESSIONID在tomcat是找不到對應session的
    5)認證過程還有其餘邏輯,此處作了刪減,不影響主體流程
  • 梳理以後,發現兩者之間雖然key一致可是值不一樣,並且彼此會干擾,因而懷疑外圍代碼調用了getSession方法,搜索代碼未找到
  • 查看請求路徑對應方法,方法中使用了session參數,可是方法體中未使用該參數,聯想到spring的參數值自動映射機制(見ServletRequestMethodArgumentResolver),在爲session賦值的過程當中調用了getSession方法,進而因爲在tomcat找不到對應session而新建、回寫相關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();
        // 略去部分代碼
    }
}
複製代碼
  • 刪除參數,測試,問題解決

瀏覽器

  • 使用該認證機制的服務的業務方法中不要使用tomcat的session機制(如getSession或session參數),不然就會出現這個問題

解決方案緩存

  • 針對此問題,將session參數去掉
  • 將認證機制的JSESSIONID換成另一個名字,可是改動量比較大,可能會有其餘坑
  • 走分佈式session(token)解決方案,將tomcat的session管理機制和分佈式session(token)結合起來
  • 採用jwt token形式鑑權,服務端不保存token,僅作驗證和刷新
歡迎關注個人微信公衆號

68號小喇叭
相關文章
相關標籤/搜索