session深刻探討

簡介

       session,會話,實際上是一個容易讓人誤解的詞。它總跟web系統的會話掛鉤,利用session,javaweb項目實現了登陸狀態的控制。坊間流傳,關閉瀏覽器,就是關閉了web系統的會話。其實瀏覽器對於會話有本身的定義,而web系統對於會話也有本身的定義。在tomcat中,session一般是指實現了HttpSession接口的實現類。而且不存在關閉瀏覽器就會關閉tomcat的HttpSession這種情況。 java

       session自己並不難,若是隻是作登陸校驗之類的功能,並不須要深刻了解,但難的是session和cookie的結合使用,在不一樣狀況下瀏覽器對cookie的控制行爲所涉及到的諸多細節,我搜查了不少資料,查看過tomcat源碼,亦是沒有找到全面的概述。固然我並未看過、也不知道去哪裏看比較全面的關於瀏覽器對cookie的控制資料,若是有知道的大神,還望留言連接。本文題目,之因此說是探討,而不是瞭解或者介紹,由於我本身也卡在了某個點上,因爲時間關係,我不能花太多時間去研究,但又不忍心就此放棄,因此先記錄下來,往後有機會再研究,這期間若有大神指點,也許能讓我茅塞頓開。web

(本文來自開源中國,做者:千里明月 ,連接:https://my.oschina.net/u/3490860/blog/write/2986928)apache

session本質

        我用的是javaweb項目,所以這裏的session特指HttpSession。先來看下tomcat源碼中對session的設計,在org.apache.catalina.session包下,有以下設計:跨域

平時所用到的HttpSession的實現類就是這個standardSession。可是所獲取的HttpSession實例確是外觀類StandardSessionFacade,其屏蔽了許多方法,但也加強了安全性。HttpSession提供了一些方法,來控制session或者獲取session的狀態,如獲取session的id,獲取session的建立時間,設置session的attribute,使session失效等。值得一提的是session的attribute實際上是一個線程安全的hashMap:瀏覽器

/**
     * The collection of user data attributes associated with this Session.
     */
    protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>();

可是,建立session、根據id獲取session的方法並不在這裏,而是在一個管理器中,其設計以下:tomcat

ManagerBase是實現了Manager接口的抽象類,實現了管理session的功能。其實現子類PersistentManagerBase拓展了將session持久化的功能。可是這裏不須要講到其子類。看ManagerBase中的一段代碼:安全

/**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();

由此可知,所謂的session,其實就是一個用線程安全的hashMap存儲起來的實現了Session接口的standardSession對象,在hashmap中以其id爲key,自身爲value。服務器

再看獲取session的方法,一目瞭然:cookie

@Override
    public Session findSession(String id) throws IOException {
        if (id == null) {
            return null;
        }
        return sessions.get(id);
    }

最重要的是看其createSession方法:session

@Override
    public Session createSession(String sessionId) {

        if ((maxActiveSessions >= 0) &&
                (getActiveSessions() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    sm.getString("managerBase.createSession.ise"),
                    maxActiveSessions);
        }

        // Recycle or create a Session instance
        Session session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();
        }
        session.setId(id);
        sessionCounter++;

        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);
            sessionCreationTiming.poll();
        }
        return session;
    }

這個方法是在何時調用的呢?當瀏覽器訪問系統時,request會解析請求中攜帶的jssesionid,用它去找到存在於應用中的session,可是若是沒有找到,那麼就會調用session的建立方法,而且生成一個新的jssessionid,返回session。

總而言之,session是存在於線程安全的map中的值,能夠經過id找到,也可使用invalidate方法銷燬,但毫不會是瀏覽器關閉,就能對它進行銷燬的。

cookie簡介

        提到session,那麼cookie是不得不說的。至於cookie是什麼,我就很少說了,你們都懂。直接看其內容吧:

這是一次http請求中(http://localhost:8080/test1),包含的請求和響應信息,是對一個系統的初次訪問,用的是谷歌瀏覽器。

請求頭中,包含的Cookie信息,並無上文提到的jsessionid, 那是由於這是對系統的初次訪問,系統還沒生成session。可是訪問以後,系統就會生成一個session,並且,會在響應流中設置響應頭Set-Cookie,其值爲JESSIONID=xxx。這樣瀏覽器對localhost:8080和cookie的聯繫就有了記憶,瀏覽器會將其存儲起來,可在調試工具中看到:

那麼再次訪問http://localhost:8080/test1, 瀏覽器會主動在請求頭添加包括jsession的cookie信息

系統根據這個jsessionid找到session,也就不會在響應頭中添加Set-Cookie信息。

這裏說一下cookie中的兩個重要屬性:

  1. domain表示的是cookie所在的域,默認爲請求的地址,如網址爲www.test.com/test/test.aspx,那麼domain默認爲www.test.com。而跨域訪問,如域A爲t1.test.com,域B爲t2.test.com,那麼在域A生產一個令域A和域B都能訪問的cookie就要將該cookie的domain設置爲.test.com;若是要在域A生產一個令域A不能訪問而域B能訪問的cookie就要將該cookie的domain設置爲t2.test.com。

  2. path表示cookie所在的目錄,默認爲/,就是根目錄。在同一個服務器上有目錄以下:/test/,/test/cd/,/test/dd/,現設一個cookie1的path爲/test/,cookie2的path爲/test/cd/,那麼test下的全部頁面均可以訪問到cookie1,而/test/和/test/dd/的子頁面不能訪問cookie2。這是由於cookie能讓其path路徑下的頁面訪問。

疑點

        下面,就該說下個人疑點了。

狀況1:

        可是當我在8081的一個方法中,重定向到8080的一個路徑時,發現了奇怪的現象。

8081系統的方法以下:

@GetMapping("/test")
    public void get1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println(id);
        response.sendRedirect("http://localhost:8080/test1");
    }

8080系統的被重定向路徑以下:

@GetMapping("/test1")
    public void get11(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println(id);
    }

一、初次訪問localhost:8081/test 獲得兩次請求的信息,一次是重定向的,一次是8080的

這說明對8081系統的初次訪問,是沒有發送jsessionid信息的,而8081系統生成了一個id爲CAAB6AED34716A0394705BDE8CAC0042的session並設置到了響應頭,再次訪問8081時理應會帶上這麼一個id。

二、

這個對8080系統的請求中帶有jsessionid爲CAAB6AED34716A0394705BDE8CAC0042的cookie信息,要知道,咱們對8080的訪問也是初次的,那麼爲何會帶上jsessionid呢?並且這個jsessionid明顯是在8081系統中生成並設置到響應頭的的jsessionid。這個現象我用谷歌和edge瀏覽器分別嘗試過,都是這樣。那麼是否是說明,瀏覽器把這個重定向到localhost:8080的請求當成是同域的請求了 。

暫且放下這個疑惑,繼續往下驗證。因爲這個請求是對8080的系統的訪問,因爲是初次訪問,系統根本沒有id爲CAAB6AED34716A0394705BDE8CAC0042的session,所以只好生成一個新的session,在響應頭中增長Set-Cookie。

三、再次訪問localhost:8081/test,這時根據上文說的,「再次訪問8081時理應會帶上這麼一個id」,也就是在cookie中帶上JSESSION=CAAB6AED34716A0394705BDE8CAC0042,  可是,我發現它帶的倒是在系統8080中生成的BA0D2C939ADEC087C0A5F0C9B3354891 !!!

這就致使了8081找不到session又再次生成了一個新的session,循環往復,每次對8081的訪問都會產生新的session。而這狀況,我以爲很明顯,是瀏覽器把對8081的訪問當成是於8080同源的了。

基於此推論,我模擬了另外一種實驗狀況,去掉重定向的功能

狀況2:

在本地開兩個web服務,端口分別是8080,8081。

localhost:8081/test

@GetMapping("/test")
    public void get1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println(id);

    }

localhost:8080/test1

@GetMapping("/test1")
    public void get11(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String id = session.getId();
        System.out.println(id);
    }

一、第一次訪問8081/test

沒有cookie,服務器設置set-cookie,正常。

二、第二次訪問8081/test

cookie與上次的set-cookie一致,正常。

三、第一次訪問8080/test1

瀏覽器把8081/test的cookie發過去了。8080的服務器找不到這個jsessionid,又從新設置了jsessionid,等到再次訪問8081/test時,你們也能猜到會發生什麼了吧。

推論

至此,我斗膽推論,瀏覽器會對同一ip不一樣端口的服務訪問認定是能夠進行cookie共享的,兩個cookie的domain是一致的。而這種cookie的截圖也必定程度上印證了個人想法:

cookie的domain彷佛只認定域名,無關端口。

可是根據瀏覽器的同源策略,同域名不一樣端口的訪問也應該是跨域的啊。除非瀏覽器的域跟cookie的domain在概念上是有區別的,對於這點,我沒找到確切的官方資料,但網上大神是這麼說的——

解決方案

        基於上面的未查閱官方資料而作出的不嚴謹的推論,我想,只要徹底避免同域的狀況就能夠避開這個問題。因而我把8081和8080系統分別部署在兩個機器上。因爲不一樣ip,這樣不管如何,兩個cookie都不會是同domain的了。果真,結果是沒有問題的。

不足

        雖然這個解決方案避開了同域的問題,可是沒有完全解決,畢竟同域的系統相互之間的訪問也是有必要的,爲此但願能得到更多的建議或者資料,補充這方面知識的不足,讓我完全解決這個問題。

 

老闆說我長得很帥,給我發紅包了!我以爲你們都很帥,我要分享給你們,歡迎掃碼領紅包——

相關文章
相關標籤/搜索