1. 程式人生 > 實用技巧 >Cookie與Session

Cookie與Session

1. Cookie

1.1 會話追蹤

一次客戶端與服務端之間的會話可能會包含多次請求和響應,而在一個會話的多個請求之間共享資料,這就叫會話跟蹤技術。在web中會話跟蹤的實現要依靠cookie和session。

1.2 cookie的由來

1)保持狀態

HTTP協議是無狀態的,每一次的請求都沒有直接關係,對伺服器來說,每一次的請求都是全新的。

而我們需要保持每次一會話產生的資料(保持狀態),所以就用到了cookie技術。

2)cookie為何物

cookie是伺服器傳送出來儲存在瀏覽器上的一組組鍵值對,下一次訪問伺服器時瀏覽器會自動攜帶這些鍵值對,以讓伺服器提取資訊。

可以用Ctrl + Shift + del三個鍵來清除頁面快取和cookie。

1.3 cookie的原理

1)圖示

  • 瀏覽器訪問服務端時,帶著一個空的cookie,然後由伺服器產生內容,瀏覽器收到響應後儲存在本地;
  • 當瀏覽器再次訪問時,會自動帶上cookie,這樣伺服器就能通過cookie的內容來判斷出客戶端身份了。

2)cookie的規範

  • cookie大小上限為4KB;
  • 一個伺服器最多在客戶端瀏覽器上儲存20個Cookie;
  • 一個瀏覽器最多儲存300個Cookie;

上面是HTTP的Cookie規範,並非絕對法則,各個瀏覽器可能會對Cookie進行擴充套件。

不同的瀏覽器之間是不共享Cookie的

3)cookie與HTTP頭

Cookie是通過HTTP請求和響應頭在客戶端和伺服器端傳遞的。

  • 請求頭(客戶端傳送給服務端)Cookie:a=xxxx; b=ooo; c=yyyy
    • 多個Cookie用分號隔開
  • 響應頭(服務端傳送給客戶端)Set-Cookie:a=xxx Set-Cookie:b=ooo Set-Cookie:c=yyy
    • 一個Cookie物件用一個Set-Cookie

4)cookie的覆蓋

  • 如果服務端傳送重複的Cookie,那麼會覆蓋原有的Cookie。

2. Django中操作Cookie

2.1 獲取Cookie

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt=''
, max_age=None)

引數

  • default:預設值
  • salt:加密鹽
  • max_age:後臺控制過期時間

2.2 設定Cookie

rep = HttpResponse(...)
rep = render(request, ...)

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密鹽', max_age=None, ...)

引數:

  • key:鍵
  • value:值
  • max_age=None
    • 超時時間,單位是秒,預設時長是2周
    • None表示這個cookie會延續到瀏覽器關閉為止
  • expires=None
    • 超時時間(IE requires expires, so set it if hasn't been already.)
    • 值是一個datatime型別的時間日期物件,到這個日期就失效的意思
  • path='/'
    • Cookie生效的路徑
    • 瀏覽器只會把cookie回傳給帶有該路徑的頁面,這樣可以避免將cookie傳給站點中的其他應用
    • / 根路徑的cookie可以被任何url的頁面訪問
  • domain=None
    • Cookie的生效域名,如果設定為None,cookie就只能由設定它的站點讀取
    • 可以用這個引數來構造一個跨站Cookie,如 domain=".example.com"所構造的cookie對它的子域站點都是可讀的
  • secure=False
    • 是否使用https來回傳cookie
  • httponly=False
    • 只能http協議傳輸,無法被JavaScript獲取(可以抓包獲取)

注意:

  • Cookie在設定時不允許出現中文
  • 如果非要設定中文,可以對中文 .encode('utf-8').decode('iso-8859-1')或者直接 json.dumps('傻X')

2.3 刪除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 刪除使用者瀏覽器上之前設定的usercookie值
    return rep

2.4 Cookie登入校驗示例

def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        next_url = request.get_full_path()
        if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
            # 已經登入的使用者...
            return func(request, *args, **kwargs)
        else:
            # 沒有登入的使用者,跳轉剛到登入頁面
            return redirect("/login/?next={}".format(next_url))
    return inner


def login(request):
    if request.method == "POST":
        username = request.POST.get("username")
        passwd = request.POST.get("password")
        if username == "xxx" and passwd == "dashabi":
            next_url = request.GET.get("next")
            if next_url and next_url != "/logout/":
                response = redirect(next_url)
            else:
                response = redirect("/class_list/")
            response.set_signed_cookie("login", "yes", salt="SSS")
            return response
    return render(request, "login.html")

3. Session

3.1 由來

Cookie雖然在一定程度上解決了“保持狀態”的需求,但是由於Cookie本身最大隻支援4096位元組,且Cookie本身儲存在客戶端,可能被攔截或竊取。因此就需要一種新的技術,它能支援更多的位元組,並且它儲存在伺服器端,有較高的安全性。

3.2 Session為何物

Session是服務端的技術,伺服器在執行時為每一個使用者的瀏覽器建立一個其獨享的session物件。

由於session為使用者瀏覽器獨享,所以使用者在訪問伺服器的web資源時,可以把各自的資料放在各自的session中。當用戶再去訪問該伺服器中的其他web資源時,其他web資源再從使用者各自的session中取出資料。

3.3 Session的原理

給每一個Cookie分配一個唯一的id,使用者訪問時,通過Cookie,伺服器就知道來將的身份。然後再根據不同的Cookie的id,在伺服器上儲存一段時間的私密資料(賬號密碼等)。這時Cookie就起到了橋接的作用。

由此,我們通過Cookie來識別不同的使用者,然後在對應的session中儲存私密的資訊。

4. Django中操作session

4.1 Django中Session的方法

# 獲取、設定、刪除Session中資料#取值

# 獲取值
request.session['k1'] 
request.session.get('k1',None) 
# request.session這句是從cookie裡面將sessionid的值取出來
# 將django-session表裡面的對應sessionid的值的那條記錄中的session-data欄位的資料拿出來(並解密)
# get方法就取出k1這個鍵對應的值

# 設定值
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在則不設定
# 生成隨機字串,將這個隨機字串和使用者資料(加密後)和過期時間儲存到了django-session表裡面
# 將這個隨機字串以sessionid:隨機字串的形式新增到cookie裡面返回給瀏覽器,這個sessionid名字是可以改的
# 注意:django-session表不能通過orm來直接控制,因為models.py裡面沒有這個對應關係

#刪除值
del request.session['k1']  #django-session表裡面同步刪除


# 所有 鍵、值、鍵值對
request.session.keys()
request.session.values()
request.session.items()


# 會話session的key
session_key = request.session.session_key  # 獲取sessionid的值

# 將所有session失效日期小於當前日期的資料刪除,將過期的刪除
request.session.clear_expired()

# 檢查會話session的key在資料庫中是否存在
request.session.exists("session_key") #session_key就是那個sessionid的值

# 刪除當前會話的所有Session資料
request.session.delete()
  
# 刪除當前的會話資料並刪除會話的Cookie。
request.session.flush()  # 常用,清空所有cookie---刪除session表裡的這個會話的記錄,
# 這用於確保前面的會話資料不可以再次被使用者的瀏覽器訪問
# 例如,django.contrib.auth.logout() 函式中就會呼叫它

# 設定會話Session和Cookie的超時時間
request.session.set_expiry(value)
    # 如果value是個整數,session會在些秒數後失效
    # 如果value是個datatime或timedelta,session就會在這個時間後失效
    # 如果value是0,使用者關閉瀏覽器session就會失效
    # 如果value是None,session會依賴全域性session失效策略

4.2 Session詳細流程解析

4.3 Session版的登入驗證

from functools import wraps
def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        next_url = request.get_full_path()
        if request.session.get("user"):
            return func(request, *args, **kwargs)
        else:
            return redirect("/login/?next={}".format(next_url))
    return inner

def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        if user == "alex" and pwd == "alex1234":
            # 設定session
            request.session["user"] = user
            # 獲取跳到登陸頁面之前的URL
            next_url = request.GET.get("next")
            # 如果有,就跳轉回登陸之前的URL
            if next_url:
                return redirect(next_url)
            # 否則預設跳轉到index頁面
            else:
                return redirect("/index/")
    return render(request, "login.html")

@check_login
def logout(request):
    # 刪除所有當前請求相關的session
    request.session.delete()
    return redirect("/login/")

@check_login
def index(request):
    current_user = request.session.get("user", None)
    return render(request, "index.html", {"user": current_user})

注意:

  • 在同一個瀏覽器上,第一個使用者已經登入,當另一個使用者也登入時,會帶著第一個使用者的session_id,但是其他內容都將會覆蓋
  • 一個網站對一個瀏覽器,是一個sessionid,換一個瀏覽器,肯定會生成另外一個sessionid

4.4 Django中Session配置

  • Django中預設支援Session,它內部提供了5中型別的Session
# 1. 資料庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'     # 引擎(預設)

# 2. 快取Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的快取別名(預設記憶體快取,也可以是memcache),此處別名依賴快取的設定

# 3. 檔案Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'   # 引擎
SESSION_FILE_PATH = None                                   # 快取檔案路徑,如果為None,則使用tempfile模組獲取一個臨時地址tempfile.gettempdir() 

# 4. 快取+資料庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

# 5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

# 其他公用設定項:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie儲存在瀏覽器上時的key,即:sessionid=隨機字串(預設)
SESSION_COOKIE_PATH = "/"                               # Session的cookie儲存的路徑(預設)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie儲存的域名(預設)
SESSION_COOKIE_SECURE = False                            # 是否Https傳輸cookie(預設)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支援http傳輸(預設)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(預設)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否關閉瀏覽器使得Session過期(預設)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次請求都儲存Session,預設修改之後才儲存(預設)