網(wǎng)絡原理2.HTPP Session, Cookie和Token詳解

一. 為什么要使用Cookie,Session或者Token

  1. http協(xié)議是無狀態(tài)的,對于服務器而言,每次請求都是一個全新的請求
  2. 實際應用中,服務器要驗證請求對應的用戶信息,請求是否合法等
  3. 可以通過Cookie和Session驗證用戶信息
  4. 也可以通過token驗證用戶身份

二. Cookie和Session關聯(lián)

http_cookie_session.png
  1. Session保存在服務器中,用于記錄會話,而Cookie保存在瀏覽器中,用于跟蹤服務器的會話
  2. 每個session都有一個Id,新建session的時候,服務器會自動生成一個sessionid
  3. 服務器設置Set-Cookie響應頭,將sessionid返回給瀏覽器
  4. 瀏覽器根據(jù)Set-Cookie響應頭,新建一個Cookie,name為jsessionid(Java Servlet默認值,也可以自定義),value為session的id
  5. 瀏覽器的每次請求,都會攜帶Cookie值
  6. 服務器根據(jù)jsessionid這個Cookie找到對應的session,獲取用戶信息,判斷請求是否合法
  7. 如果瀏覽器禁用了Cookie,那么可以通過URL重定向的方式,將SessionID編碼到URL中,獲取服務器Session

1. Tomcat Session和Cookie源碼剖析

1.1 Tomcat Request類

主要是通過cookie獲取session,如果session不存在的話,則創(chuàng)建一個

//Tomcat Request類
public class Request implements HttpServletRequest {
    
    /**
     * 獲取請求對應的Session
     * 如果session不存在,則創(chuàng)建一個
     */
    @Override
    public HttpSession getSession() {
        Session session = doGetSession(true);
        if (session == null) {
            return null;
        }
        return session.getSession();
    }
    
    /**
     * 獲取Session
     * @param create 是否創(chuàng)建
     */
    protected Session doGetSession(boolean create) {
        //獲取請求上下文
        Context context = getContext();
        if (context == null) {
            return null;
        }
        if ((session != null) && !session.isValid()) {
            //session過期了
            session = null;
        }
        if (session != null) {
            return session;
        }

        Manager manager = context.getManager();
        if (manager == null) {
            return null;
        }
        if (requestedSessionId != null) {
            //請求攜帶的requestedSessionId,也就是Cookie中的jsessionid
            try {
                //ManagerBase對象中獲取Session
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid()) {
                session = null;
            }
            if (session != null) {
                session.access();
                return session;
            }
        }

        if (!create) {
            return null;
        }
        if (response != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)
                && response.getResponse().isCommitted()) {
            throw new IllegalStateException(
                    sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            //從Cookie中取出SessionId
            if (context.getValidateClientProvidedNewSessionId()) {
                boolean found = false;
                for (Container container : getHost().findChildren()) {
                    Manager m = ((Context) container).getManager();
                    if (m != null) {
                        try {
                            if (m.findSession(sessionId) != null) {
                                found = true;
                                break;
                            }
                        } catch (IOException e) {
                          
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
            }
        } else {
            sessionId = null;
        }
        session = manager.createSession(sessionId);

        //生成一個關聯(lián)Session的Cookie
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());
            //將cookie添加到response中
            response.addSessionCookieInternal(cookie);
        }

        if (session == null) {
            return null;
        }

        session.access();
        return session;
    }
}
1.2 Tomcat ManagerBase類

維護了所有的session

public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
    
    //保存sessiond的map
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();

    /**
     * 通過sessionid獲取session
     */
    public HashMap<String, String> getSession(String sessionId) {
        //從map中獲取
        Session s = sessions.get(sessionId);
        if (s == null) {
            if (log.isInfoEnabled()) {
                log.info(sm.getString("managerBase.sessionNotFound", sessionId));
            }
            return null;
        }

        Enumeration<String> ee = s.getSession().getAttributeNames();
        if (ee == null || !ee.hasMoreElements()) {
            return null;
        }
        
        //將session中的值放到另一個map中
        //并返回新的map
        HashMap<String, String> map = new HashMap<>();
        while (ee.hasMoreElements()) {
            String attrName = ee.nextElement();
            map.put(attrName, getSessionAttribute(sessionId, attrName));
        }

        return map;
    }
}
1.3 tomcat Response類

主要通過過Set-Cookie請求頭設置瀏覽器Cookie,還可以設置Cookie中維護的SessionId

/**
 * tomcat Response類
 */
public class Response implements HttpServletResponse {
    
    /**
     * 添加cookie到瀏覽器
     */
    @Override
    public void addCookie(final Cookie cookie) {
        if (included || isCommitted()) {
            return;
        }

        cookies.add(cookie);

        String header = generateCookieString(cookie);
        //設置Set-Cookie響應頭
        addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
    }

    /**
     * 將cookie添加到response中
     */
    public void addSessionCookieInternal(final Cookie cookie) {
        if (isCommitted()) {
            return;
        }

        String name = cookie.getName();
        //在Set-Cookie響應頭中設置cookie
        final String headername = "Set-Cookie";
        final String startsWith = name + "=";
        String header = generateCookieString(cookie);
        boolean set = false;
        MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
        int n = headers.size();
        for (int i = 0; i < n; i++) {
            if (headers.getName(i).toString().equals(headername)) {
                if (headers.getValue(i).toString().startsWith(startsWith)) {
                    headers.getValue(i).setString(header);
                    set = true;
                }
            }
        }
        if (!set) {
            addHeader(headername, header);
        }
    }
    
    //如果瀏覽器禁用了Cookie
    //那么可以使用該方法,將sessionId編碼到URL中
    @Override
    public String encodeRedirectURL(String url) {
        if (isEncodeable(toAbsolute(url))) {
            return toEncoded(url, request.getSessionInternal().getIdInternal());
        } else {
            return url;
        }
    }
}

三. Token的使用

Token又被稱為令牌,主要用于校驗用戶身份,也是我們當前推薦使用的一種認證方式

Token相比于Cookie的優(yōu)勢

  1. 無狀態(tài),服務器上不需要存儲用戶對應的Session數(shù)據(jù),Token可以包含用戶信息
  2. 支持跨域訪問,Cookie默認是不支持跨域訪問,允許跨域可能會導致CSRF攻擊
  3. 解耦,將授權服務和應用服務解耦,只要token正確
  4. 安全性更高,可以采用各種加密方式來加密token,也可以有效避免CSRF攻擊
  5. 有利于前后端分離,H5,APP各個應用都可以使用同一的授權驗證模塊
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杉女,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸳吸,更是在濱河造成了極大的恐慌熏挎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌砾,死亡現(xiàn)場離奇詭異坎拐,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門哼勇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來都伪,“玉大人,你說我怎么就攤上這事积担≡耗纾” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵磅轻,是天一觀的道長。 經(jīng)常有香客問我逐虚,道長聋溜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任叭爱,我火速辦了婚禮撮躁,結果婚禮上,老公的妹妹穿的比我還像新娘买雾。我一直安慰自己把曼,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布漓穿。 她就那樣靜靜地躺著嗤军,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晃危。 梳的紋絲不亂的頭發(fā)上叙赚,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音僚饭,去河邊找鬼震叮。 笑死,一個胖子當著我的面吹牛鳍鸵,可吹牛的內(nèi)容都是我干的苇瓣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼偿乖,長吁一口氣:“原來是場噩夢啊……” “哼击罪!你這毒婦竟也來了?” 一聲冷哼從身側響起贪薪,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤外邓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后古掏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體损话,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丧枪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片光涂。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拧烦,靈堂內(nèi)的尸體忽然破棺而出忘闻,到底是詐尸還是另有隱情,我是刑警寧澤恋博,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布齐佳,位于F島的核電站,受9級特大地震影響债沮,放射性物質(zhì)發(fā)生泄漏炼吴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一疫衩、第九天 我趴在偏房一處隱蔽的房頂上張望硅蹦。 院中可真熱鬧,春花似錦闷煤、人聲如沸童芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽假褪。三九已至,卻和暖如春近顷,著一層夾襖步出監(jiān)牢的瞬間嗜价,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工幕庐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留久锥,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓异剥,卻偏偏與公主長得像瑟由,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子冤寿,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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