tornado 实现把 session 存储到 redis

作为SA,我们肯定会自己写各种运维系统实现自动化,而作为一个运维系统是肯定需要登陆的,so今天我想尝试一下用 tornado 实现一个需要登陆的网站,这一次我想把 session 存储在 redis 里,这样就不用在负载均衡层(比如lvs、haproxy、nginx) 做会话保持,更加通用。

 

这里忽略注册的部分,只说下登陆和session存储的部分,我设计的大概步骤如下:

1. 用户第一次访问,发现没登陆,要求用户登陆。

怎么发现用户没登陆呢,查看 cookie 没有 session_id 和 hmac_key  即表示 用户没登陆。

服务器端获取 session_id 和 verification 的代码:
    session_id = request_handler.get_secure_cookie(“session_id”)
    hmac_key = request_handler.get_secure_cookie(“verification”)

这里用 verification 的目的是验证 session_id 的正确性,verification 是用 session_id 生成的,具体代码如下:
def _generate_hmac(session_id):
    return hmac.new(session_id, session_secret, hashlib.sha256).hexdigest()

发现没登陆之后就跳转到 /login 页面。

 

2. 在 /login 页面,用户输入用户名和密码(有一个可选项: 30天内自动登陆)之后,服务器验证通过,然后给用户生成一个 session_id ,最后 把 session_id 和 用户名 保存在 redis 中。如果选择了 30天内自动登陆 ,那么过期时间设置成 30 * 24 * 3600 。

验证的代码就不说了,就是去读账号数据库。

验证通过后,给用户生成一个 session_id,生成代码:
def _generate_id():
    new_id = hashlib.sha256(session_secret + str(uuid.uuid4()))
    return new_id.hexdigest()

session_secret 我们自己设置。

把 session_id 和 用户名 保存在redis 里,用:
session_timeout = 30 * 24 * 3600
session_data = {“user_name”: user_name}
redis_client.setex(session_id, session_data, session_timeout)

在 session_timeout 之后,redis 会自动把这条记录删除。

保存之后,set_secure_cookie 一下,此时请求返回之后在客户端的 Cookie里面可以看到 session_id 和 hmac_key (加密了) 了。
request_handler.set_secure_cookie(“session_id”, session_id, expires_days=30)
request_handler.set_secure_cookie(“verification”, hmac_key, expires_days=30)

这里我们用set_secure_cookie,tornado会对Cookie进行加密,加密的key 叫做 cookie_secret ,在 tornado.web.Application 初始化的时候传入。

另外,set_secure_cookie 有个参数 expires_days,表示Cookie的过期时间,默认是30天,如果 expires_days = None,表示它是一个 a session cookie (浏览器关闭就过期)。

所以 set_secure_cookie 的 expires_days 最好 和 session_timeout 一致。

 

3. 下次用户再请求的时候,服务器会自动从 header 获取到 session_id 和 verification ,然后判断session_id 是否有效和过期。

判断 session_id 是否存在 和 session_id 和 verification 是否吻合:
session_id = request_handler.get_secure_cookie(“session_id”)
hmac_key = request_handler.get_secure_cookie(“verification”)

if session_id == None:
    session_exists = False
else:
    session_exists = True

if session_exists == True:
    check_hmac = self._generate_hmac(session_id)
if hmac_key != check_hmac:
    pass

另外,需要根据session_id 获取 user_name,此时从redis里面获取,如果为{},表示已经session过期了(跳到 /login) 。
def _fetch(self, session_id):
    try:
        session_data = redis_client.get(session_id)

    if type(session_data) == type({}):
        return session_data
    else:
        return {}
    except IOError:
        return {}

 

 

参考:

https://github.com/zs1621/tornado-redis-session