19

基础概念

Session管理是Web Application的基础也是一个老生常谈的话题。为了方便后文的展开,更重要的是确认自己清晰的理解了整个Session管理的概念,我在此还是决定赘述的整个流程。如果你已经对于Session概念非常清晰的话,可以跳过本节不影响对于后文的理解。

HTTP协议在设计的时候是无状态的。这是一个很关键的概念,意味着服务器在处理请求的时候,并不关注这个请求是谁发来的。这对于以提供内容为核心的Web1.0,例如门户网站,非常适合。然而对于以应用服务为核心的Web 2.0而言,服务器端必须有能力从请求中提取出请求者的身份信息,以在请求者不用反复输入身份的情况下,提供连续的服务。

实现请求身份验证的方式很多,其中一种广泛接受的方式是使用服务器端产生的Session ID结合浏览器的Cookie实现对Session的管理,一般来说包括以下4个步骤:

  1. 服务器端的产生Session ID
  2. 服务器端和客户端存储Session ID
  3. 从HTTP Header中提取Session ID
  4. 根据Session ID从服务器端的Hash中获取请求者身份信息

图片描述

上图是一个使用Redis Cluster来实现对Session管理的流程,但本质上除了redisMatrix.get和redisMatrix.set以外,和一般的Session管理流程是一致的。

简单来说,一个请求到达的时候,服务器会先判断是否带有Session信息。如果有,则根据Session ID去数据库中查找是否具有对应的用户身份信息。此处可能会出现Session失效、非法的Session信息等可能性,那么服务器视同无Ssession信息的情况,重新的产生一个随机的字符串,并且在Http返回头中写入新的Session ID信息。另一者,如果服务器成功获取了用户的身份信息则以该身份为请求者提供服务。

使用Express和Redis对Session管理的实现

Redis是一个非常适合用于Session管理的数据库。第一,它的结构简单,key-value的形式非常符合SessionID-UserID的存储;第二,读写速度非常快;第三,自身支持数据自动过期和清除;第四,语法、部署非常简单。基于以上原因,很多Session管理都是基于Redis实现的。
Express已经将Session管理的整个实现过程简化到仅仅几行代码的配置的地步了,你完全不用理解整个session产生、存储、返回、过期、再颁发的结构,使用Express和Redis实现Session管理,只要两个中间件就足够了:

废话不多说还是上代码:

var express = require('express');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);

var app = express();
var options = {
     "host": "127.0.0.1",
     "port": "6379",
     "ttl": 60 * 60 * 24 * 30,   //Session的有效期为30天
};

// 此时req对象还没有session这个属性
app.use(session({
     store: new RedisStore(options),
     secret: 'express is powerful'
}));
// 经过中间件处理后,可以通过req.session访问session object。比如如果你在session中保存了session.userId就可以根据userId查找用户的信息了。

req在经过session中间件的时候就会自动完成session的有效性验证、延期/重新颁发、以及对session中数据的获取了。

上述代码只是对于请求的Session静态处理,整个用户管理的另一个方面则是状态的切换(用户的登陆、登出)以及用户数据的获取。

exports.signin = function(params, req, res){
    var username = params.username;
    var password = params.password;

    //查找用户信息,看是否满足登陆条件
    var user = findUser(username, password);
    if(user){
        //成功获取用户对象
        req.session.regenerate(function(){
            req.user = user;
            req.session.userId = user.id;
            req.session.save();  //保存一下修改后的Session

            res.redirect('/account');
        });  
    }
    else{
        //用户信息不符合,登陆失败
    }
}

exports.signout = function(req, res){
    req.clearCookie('connect.sid');
    req.user = null;

    req.session.regenerate(function(){
        //重新生成session之后后续的处理
        res.redirect('/signin');
    })
}

exports.persist = function(req, res, next){
    var userId = req.session.userId;

    //通过user id去数据库里面查找User对象
    var user = findUserById(userId);

    if(user){
        req.user = user;
        next();
    }
    else{
        //该用户不存在
    }
}

Session的安全问题

SessionId就如同请求者的身份证,一旦被攻击者恶意获得,攻击者便可以伪装成请求者对服务器发起请求,也就是我们经常听到的会话劫持(Session/Cookie Hijack)
关于会话劫持的原理推荐大家去看这篇文章

基于上述实现方法的Session管理,我认为基本上可以排除

  • 暴力破解SessionId
  • 恶意植入固定SessionId

两种可能,因为uid的库基本上可以保证SessionId的随机性;而传递SessionId则依赖HTTP请求头中的Cookie信息而非URL,同时在用户登录立刻更换SessionId。

唯一的可能性来源于Session的监听,而对于这种攻击有效的两种防止办法是:

  • Https
    很多网站仅仅在Login的阶段使用Https防止用户的用户名、密码信息被监听者获取,但是随后的SessionId同样有可能被监听者获取而伪造登录者的身份信息。因此更加推荐的方式是所有的信息传递全部使用Https实现,这样即使监听着截获了信息也无法破解其中的内容。关于如何使用NodeJS建立一个HTTPS的server可以参考《HTTPS的原理和NodeJS的实现》 这篇文章
  • httpOnly
    Express在options中提供了httpOnly的属性,此属性默认值为true,这个属性保证了Cookie的信息不能够通过JavaScript脚本获取。

yeelan0319
1k 声望49 粉丝

虽然我是这个世界的晚来者,但是却会比任何人更快的前进,只是因为我热爱这个世界。