使用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访问服务端的隐私数据。
惊喜
无意中,在snoopyXDY的文章中看到了用ETag进行兼容,大喜,并感慨于该方法的奇妙。(之前遇到过ETag在服务器集群中同步的问题,原因是在服务端生成ETag的方式不妥,最终解决方案就是针对请求文件的内容进行hash并base64编码,这样在服务端同步的前提下,请求任意服务器都会返回相同的ETag)在此处实现中,则是异步请求一个js文件,该文件会根据客户端的相关头部设置或获取session,并且在服务端触发客户端的相关事件,完成数据的传递。
github地址:https://github.com/royalrover/ETag-Session
揭秘
首先,我们访问登陆页面login.html,在页面底部的script中,异步加载一个名称为‘eTag.js’的文件,这个文件并不是静态的,而是由服务端根据客户端传递的参数进行相应处理:如果客户端的request头部有‘if-none-match’字段,则会在内存中查看是否有该字段对应的value(服务端用hash进行存储各个客户的的session),并将该value值序列化,同时触发客户端的‘etag-ready’事件,并将序列化的value作为值传入。
在这里的实现中,有可能会存在浏览器对动态文件‘eTag.js’的缓存,为了避免‘eTag.js’的准确和实时,因此需要设置‘cache-control’头部。
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'事件,并在客户端进行逻辑处理和渲染。
客户端的逻辑如下:
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被禁用的一种候补方案,不推荐大规模使用。
使用ETag进行session的降级的更多相关文章
- cookie/session在nodes中的实战
cookie 和 session 众所周知,HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据,如何能把一个用户的状态数据关联起来呢? 比如在淘宝的某个 ...
- Redis安装及实现session共享
一.Redis介绍 1.redis是key-value的存储系统,属于非关系型数据库 2.特点:支持数据持久化,可以让数据在内存中保存到磁盘里(memcached:数据存在内存里,如果服务重启,数据会 ...
- web浏览器下的缓存 - Etag
设置浏览器缓存的几种方法: Last-Modified : 服务器上文件的最后修改时间 Etag : 文件标识 Expiers : 本地缓存目录中文件过期的时间 ( 由服务器指定具体的时间 ) Ca ...
- 用PHP操作http中Etag、lastModified和Expires标签
http://blog.hehehehehe.cn/a/10994.htm 客户端通过浏览器发出第一次请求某一个URL时,根据 HTTP 协议的规定,浏览器会向服务器传送报头(Http Request ...
- SDP (Session Description Protocol)
SDP的描述网络上一大堆中文的,可以看看RFC2327 SDP 信息是文本信息,采用 UTF-8 编 码中的 ISO 10646 字符集.SDP 会话描述如下:(标注 * 符号的表示可选字段): v ...
- HTTP协议----ETag
Etag是URL的Entity Tag,用于标示URL对象是否改变,区分不同语言和Session等等.具体内部含义是服务器控制的,就像Cookie那样. 服务器首先产生ETag,服务器可在稍后使用它来 ...
- HTTP协议缓存策略深入详解之ETAG妙用
Etag是什么: Etag 是URL的Entity Tag,用于标示URL对象是否改变,区分不同语言和Session等等.具体内部含义是使服务器控制的,就像Cookie那样. HTTP协议规格说明定义 ...
- Last-Modified和ETag以及Apache和Nginx中的配置
1) 什么是”Last-Modified”? 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修 ...
- sonar + jacoco + mockMvc 模拟session 用户登录 配合SpringSecurity 权限 快速测试代码覆盖率.
遇到mock 测试简直就是神器,特别是要做代码覆盖率,直接测试controller就好了,缺点,虽然可以回滚事务,但是依赖数据库数据,解决,根据SpringBoot ,再建立一个专门跑单元测试的数据库 ...
随机推荐
- 异步任务队列Celery在Django中的使用
前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务.在同事的指引下接触了Celery这个异步任务队 ...
- 简单粗暴地理解js原型链--js面向对象编程
原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧, ...
- webpack入门教程之Hello webpack(一)
webpack入门教程系列为官网Tutorials的个人译文,旨在给予想要学习webpack的小伙伴一个另外的途径.如有不当之处,请大家指出. 看完入门教程系列后,你将会学习到如下内容: 1.如何安装 ...
- 【翻译】MongoDB指南/CRUD操作(四)
[原文地址]https://docs.mongodb.com/manual/ CRUD操作(四) 1 查询方案(Query Plans) MongoDB 查询优化程序处理查询并且针对给定可利用的索引选 ...
- 页面中多个script块之间的关系
一:函数声明与函数定义表达式在函数调用间的区别 <script type="text/javascript"> doA(); var doA = function(a ...
- [APUE]文件和目录(中)
一.link.unlink.remove和rename 一个文件可以有多个目录项指向其i节点.使用link函数可以创建一个指向现存文件连接 #include <unistd.h> int ...
- [.NET] 打造一个很简单的文档转换器 - 使用组件 Spire.Office
打造一个很简单的文档转换器 - 使用组件 Spire.Office [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6024827.html 序 之前,& ...
- 二叉树的递归实现(java)
这里演示的二叉树为3层. 递归实现,先构造出一个root节点,先判断左子节点是否为空,为空则构造左子节点,否则进入下一步判断右子节点是否为空,为空则构造右子节点. 利用层数控制迭代次数. 依次递归第二 ...
- Android AppBar
AppBar官方文档摘记 2016-6-12 本文摘自Android官方文档,为方便自己及其他开发者朋友阅读. 章节目录为"Develop > Training > Best P ...
- 我的MYSQL学习心得(十四) 备份和恢复
我的MYSQL学习心得(十四) 备份和恢复 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) ...