手寫一個tornado框架session功能

在web開發的時候由於咱們常常須要用到cookie來識別和保存用戶狀態,可是cookie又不是很安全,不少信息直接明文就暴露了,這個時候session就派上用場了,而python web框架tornado就沒有自帶的session功能,這個時候我本身手擼一個出來,其實瞭解session的原理的話仍是很好寫的。python

什麼是session?git

咱們知道由於http鏈接是無狀態的,客戶端鏈接服務器請求完數據後便斷開鏈接,下一次鏈接的時候服務器端便不知道你是誰,那麼服務器要經過怎樣知道你是誰呢,這個時候就須要cookie了,cookie等因而一個身份令牌,當你來請求服務器端的時候,服務器端會給客戶端發一個令牌,而客戶端下一次訪問的時候,就能夠帶着這個令牌來訪問服務端。這就是cookie的原理。打個比方當你經過帳戶密碼登錄網站的時候,服務端就會往你的cookie裏面寫入,is_login=true, user_id=1這種方式來記錄用戶已經登錄。當你帶着這個 cookie去訪問服務端的時候,服務端就知道你已經登錄了,而且你是用戶1。可是這種方式太簡單粗暴了,不太安全。咱們應該保證這類令牌是隨機的不可預料性以保證其最基本的安全。web

    瞭解cookie原理後再來看session原理,既然session能夠解決安全等問題,他是如何作到呢?既然cookie將一些敏感數據簡單粗暴的存儲在客戶端這樣不安全,那session就是將數據存儲在服務器端以便每次認證,並每次都會更新。json

照着這個思路咱們來看看代碼部分安全

這裏假設你已經完成了登錄功能,並建立了用戶表。服務器

首先在用戶模型中添加一個字段(個人tornado項目用的model模塊是peewee)cookie

sessions            = peewee.CharField(db_column="sessions")

看看登錄是怎麼處理的session

# 假設前面完成用戶帳戶密碼認證並獲取到用戶對象member_obj(從用戶表中獲取到的用戶對象)
        # 生成10位的隨機字符串
        sess_key = ''.join(
            random.choice(string.ascii_lowercase + string.digits) \
            for i in range(10)
        )
        # 將其保存到字典中
        session = {"id": sess_key, "time": int(time.time())}
        try:
        # 從用戶表中讀取存入的json數據並將其從json轉爲列表
            sessions = json.loads(member_obj.sessions)
        except:
            sessions = list()
        if not isinstance(sessions, list):
            sessions = list()
        # 將當前生成session key字典加入sessions list
        # 限制session list長度爲5
        sessions.append(session)
        if len(sessions) > 5:
            sessions = sessions[-5:]
        將session list轉爲json存入用戶表中的session列中
        member_obj.sessions = json.dumps(sessions)
        member_obj.save()
        # 將其寫入cookie中,以便從cookie中獲取數據後對其進行認證(這裏其實暴露了member_id你能夠對cookie value二次加密,我偷懶就沒弄)
        self.set_cookie(self.settings["cookie_key_sess"],
            str(member_obj.member_id)+":"+sess_key
        )

而後咱們能夠對tornado的 RequestHandler的get_current_user進行重寫(這是tornado預留的鉤子函數,用戶用戶認證,詳細能夠看官方文檔)app

def get_current_user(self):
        cookie_data = self.get_cookie(self.settings["cookie_key_sess"])
        if not cookie_data:
            return None

        try:
            telephone, session_id = cookie_data.split(":")
        except:
            return None
        #這個是我本身寫的Member model下的classmethod
        member = Member.get_user_by_sess(member_id, session_id)
        return member


#Member model下的classmethod

   @classmethod
    def get_user_by_sess(cls, member_id, session_id):
        member = None
        sessions = None
        try:
            member = cls.get(
                (cls.member_id == member_id) & (cls.status == 'normal') #status是用戶狀態,判斷是否被註銷
            )
            sessions = json.loads(member.sessions)
        except:
            return None

        if not member or not sessions or not isinstance(sessions, list):
            return None

        for session in sessions: #判斷用戶攜帶過來的session數據是否有效,有效返回member對象
            if isinstance(session, dict) and session.get("id") \
                    and session["id"] == session_id:
                return member
        return None
相關文章
相關標籤/搜索