使用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

相关文章
相关标签/搜索