使用ETag進行session的降級

回顧

在web後臺開發中咱們常常須要存儲一些變量到session中進行暫存,最爲特殊的就是「購物車」,因爲http的無狀態特性,所以咱們須要在客戶端打上一個標記,惟一的標示客戶端並和服務端session一一對應,所以就有了經過cookie和url進行存儲或傳遞這個標示--sessionID。
sessionID是一個長的字符串,它每每默認經過cookie來保存,這個session並不持久化到硬盤而是暫存到內存中,每次請求時都會在head中帶上這個包含sessionID的cookie,服務端能夠根據該id標示出客戶端,從而訪問服務器的一片內存區;另外一種經過url方式傳遞sessionID,當cookie在客戶端被禁用時,服務端會將生成的sessionID加入到url,以此完成sessionID的傳輸,又稱url回寫。可是url回寫會有明顯的安全漏洞,當該網站被xss注入時,攻擊者就能夠經過竊取的sessionID訪問服務端的隱私數據。javascript

驚喜

無心中,在snoopyXDY的文章中看到了用ETag進行兼容,大喜,並感慨於該方法的奇妙。(以前遇到過ETag在服務器集羣中同步的問題,緣由是在服務端生成ETag的方式不妥,最終解決方案就是針對請求文件的內容進行hash並base64編碼,這樣在服務端同步的前提下,請求任意服務器都會返回相同的ETag)在此處實現中,則是異步請求一個js文件,該文件會根據客戶端的相關頭部設置或獲取session,而且在服務端觸發客戶端的相關事件,完成數據的傳遞。
github地址:https://github.com/royalrover/ETag-Sessionhtml

揭祕

首先,咱們訪問登錄頁面login.html,在頁面底部的script中,異步加載一個名稱爲‘eTag.js’的文件,這個文件並非靜態的,而是由服務端根據客戶端傳遞的參數進行相應處理:若是客戶端的request頭部有‘if-none-match’字段,則會在內存中查看是否有該字段對應的value(服務端用hash進行存儲各個客戶的的session),並將該value值序列化,同時觸發客戶端的‘etag-ready’事件,並將序列化的value做爲值傳入。
在這裏的實現中,有可能會存在瀏覽器對動態文件‘eTag.js’的緩存,爲了不‘eTag.js’的準確和實時,所以須要設置‘cache-control’頭部。java

router.get('/_eTag_.js',function * (next){
  var ctx = this;
  var etag = ctx.header['if-none-match'];
  var cache;

  if(!etag) {
      etag = new Date().getTime() + '__etag';
  }
//    console.log(session)
  if(session[etag]) {
    cache = session[etag];
  }else {
    cache = {
        etag: etag
    };
  }
  cache = JSON.stringify(cache);
  ctx.set('ETag',etag);
  ctx.set('cache-control','no-cache');
  ctx.set('content-type','application/javascript');
//    ctx.body = 'window._session = '+ cache + ';';
  ctx.body = 'd.do("etag-ready",'+ cache +')';
//    console.log('cache: '+cache)
  yield *next;
})

在處理post請求時,node並不會解析body,所以須要咱們本身來搞定,能夠經過模塊,也能夠簡單的經過訂閱事件,在這裏我是簡單的用node原生的request對象進行偵聽。
爲了不js阻塞渲染,採用異步加載的方式獲取,但這也會形成從服務端獲取的數據不能及時被客戶端處理和渲染,爲了解決這個問題,此處採用了重量級應用必備的解決方案-Bigpipe,有服務端出發客戶端訂閱的事件,一旦服務端去到session數據,則觸發'etag-ready'事件,並在客戶端進行邏輯處理和渲染。
客戶端的邏輯以下:node

function $(n){
        return document.querySelectorAll(n);
    }

    function asyncLoad(src) {
        var s = document.createElement('script');
        s.src = src;
        s.async = true;
        document.body.appendChild(s);
    }

    function DO(){
        this.cbs = [];
    }
    DO.prototype.on = function(k,cb){
        if(!this.cbs[k]) {
            this.cbs[k] = [];
        }
        this.cbs[k].push(cb);
    };
    DO.prototype.do = function(k,data){
        if(!this.cbs[k])
        return;
        var cbs = this.cbs[k],len = cbs.length;
        for(var i=0;i<len;i++){
            cbs[i].call(this,data);
        }
    };

    asyncLoad('/_eTag_.js');
    var d = new DO();
    d.on('etag-ready',function(_session){
        console.log('etag-ready...');
        console.log(_session);
        if(_session && _session.etag && !_session.usrname) {
            $('[name=etag]')[0].value = _session.etag;
        }else {
            $('[name=usrname]')[0].value = _session.usrname;
            $('[name=pwd]')[0].value = _session.pwd;
            $('[name=etag]')[0].value = _session.etag;
        }
    })

總結

使用ETag方式來hack兼容性是很是棒的,幾乎全部的服務器都實現了這個機制(HTTP1.1規範),所以兼容性不是問題。因爲使用ETag加載的文件的元數據都保存在瀏覽器的緩存中,所以安全性是無法與存儲在內存中的cookie方式相比的,並且若是清空瀏覽器緩存,那麼客戶端則丟失sessionID,無法在使用session。所以這種方式也僅僅做爲cookie被禁用的一種候補方案,不推薦大規模使用。git

相關文章
相關標籤/搜索