在傳統的整體式 Django 網站認證中,認證更為簡單,並且涉及基於會話的 Cookie 模式,我們將在下面進行回顧。 但是使用 API 會有些棘手。 請記住,HTTP 是無狀態協議,因此沒有內置的方式可以記住用戶是否從一個請求到另一個請求進行了身份驗證。 每次用戶請求受限資源時,它都必須驗證自己。
解決方案是在每個 HTTP 請求中傳遞唯一的標識符。 令人困惑的是,此標識符的形式尚無公認的方法,它可以采用多種形式。 Django REST Framework 僅隨附了四個不同的內置身份驗證選項! 而且還有更多第三方軟件包提供了其他功能,例如 JSON Web 令牌( JSON Web Tokens,JWT)。
在本文中,我們將徹底探索 API 身份驗證的工作原理,回顧每種方法的利弊,然後為我們的 Blog API 做出明智的選擇。 到最後,我們將創建用於注冊,登錄和注銷的API端點。
HTTP 身份驗證的最常見形式稱為“基本”身份驗證。 客戶端發出 HTTP 請求時,必須在授予訪問權限之前強制發送批准的身份驗證憑據。
完整的請求/響應流如下所示:
客戶端發出 HTTP 請求
服務器以包含 401(未授權)狀態的HTTP響應進行響應代碼和 WWW-Authenticate HTTP 標頭以及有關如何授權的詳細信息
客戶端通過 Authorization HTTP 標頭發送回憑據
服務器檢查憑據並以 200 OK 或 403 Forbiddenstatus 響應碼
批准後,客戶端將使用授權 HTTP 標頭憑據發送所有將來的請求。 我們還可以將這種交換可視化如下:
請注意,發送的授權憑證是 <username>:<password>
的未加密的 base64 編碼版本。 所以在我的情況下,這是 wsv:password123
的 base64 編碼為 d3N2OnBhc3N3b3JkMTIz
。
這種方法的主要優點是簡單。 但是有幾個主要缺點。 首先,對於每個單個請求,服務器必須查找並驗證用戶名和密碼,這是低效的。 最好先進行一次查詢,然後傳遞某種表示該用戶已被批准的令牌。 其次,用戶憑證通過明文傳遞,而不是完全不加密。 這是非常不安全的。 任何未經加密的互聯網流量都可以輕松地捕獲和重用。 因此,基本身份驗證只能通過HTTPS(HTTP的安全版本)使用。
像傳統的 Django 一樣,整體式網站長期以來一直使用替代身份驗證方案,該方案將會話和 cookie 結合在一起。 在較高級別上,客戶端使用其憑據(用戶名/密碼)進行身份驗證,然後從服務器接收會話 ID(存儲為 cookie)。 然後,此會話ID將在以後的每個 HTTP 請求的標頭中傳遞。
傳遞會話 ID 後,服務器將使用它來查找會話對象,該對象包含給定用戶的所有可用信息,包括憑據。
這種方法是有狀態的,因為必須在服務器(會話對象)和客戶端(會話 ID )上都保存並維護一條記錄。
讓我們回顧一下基本流程:
用戶輸入其登錄憑據(通常是用戶名/密碼)
服務器驗證憑據是否正確,並生成一個會話對象,該會話對象,然後存儲在數據庫中
服務器向客戶端發送會話 ID,而不是會話對象本身,該會話 ID 作為 cookie 存儲在浏覽器中
在所有將來的請求中,會話 ID 都包含 HTTP 標頭,並且如果數據庫已驗證,則請求繼續
用戶注銷應用程序後,客戶端和服務器都會銷毀 session ID。
如果用戶以後再次登錄,則會生成一個新的 session ID ,並將其作為 cookie 存儲在客戶端上
Django REST Framework 中的默認設置實際上是基本身份驗證和會話身份驗證的組合。 使用 Django 的傳統的基於會話的認證系統,並通過基本身份驗證在每個請求的 HTTP 標頭中傳遞會話 ID 。
這種方法的優點是更安全,因為用戶憑據僅發送一次,而不是像基本身份驗證那樣在每個請求/響應周期中發送一次。 由於服務器不必每次都驗證用戶的憑據,它只需將會話 ID 與會話對象進行匹配即可快速查找,因此效率更高。
但是有幾個缺點。 首先,會話 ID 僅在執行登錄的浏覽器中有效; 它不能跨多個域工作。 當 API 需要支持多個前端(例如網站和移動應用程序)時,這是一個明顯的問題。 其次,會話對象必須保持最新狀態,這在具有多個服務器的大型站點中可能是一個挑戰。 您如何在每個服務器上保持會話對象的准確性? 第三,對於每個單獨的請求,即使是不需要身份驗證的請求,都會發送 cookie,這樣效率很低。
結果,通常不建議對將具有多個前端的任何 API 使用基於會話的身份驗證方案。
第三種主要方法以及我們將在 Blog API 中實現的方法是使用 Token 身份驗證。 由於單頁應用程序的興起,這是近年來最流行的方法。
基於 Token 的身份驗證是無狀態的:客戶端將初始用戶憑據發送到服務器後,將生成唯一令牌,然後由客戶端將其存儲為 cookie 或本地存儲。 然後,此 Token 在每個傳入的 HTTP 請求的標頭中傳遞,服務器使用它來驗證用戶的身份。 服務器本身不會保留用戶記錄,只是令牌是否有效。
Cookies vs localStorage
Cookies用於讀取服務器端信息。 它們較小(4KB),並隨每個HTTP請求自動發送。 LocalStorage專為客戶端信息而設計。 它更大(5120KB),默認情況下,每個HTTP請求都不會發送其內容。 Cookie和localStorage中存儲的令牌容易受到XSS攻擊。 當前的最佳實踐是使用httpOnly和Secure cookie標志將令牌存儲在cookie中。
讓我們看一下此 challenge/response 流中的實際 HTTP 消息的簡單版本。 請注意,HTTP 標頭 WWW-Authenticate 指定在響應授權標頭請求中使用的令牌的使用。
這種方法有很多好處。 由於令牌存儲在客戶端上,因此擴展服務器以維護最新的會話對象不再是問題。 令牌可以在多個前端之間共享:同一 Token 可以代表網站上的用戶和移動應用程序上的同一用戶。 相同的會話 ID 無法在不同的前端之間共享,這是一個主要限制。
潛在的不利因素是令牌可能會變得很大。 令牌包含所有用戶信息,而不僅僅是一個會話 ID /會話對象設置的 ID。 由於令牌是在每個請求上發送的,因此管理令牌的大小可能會成為性能問題。
令牌的實施方式也可能有很大不同。 Django REST 框架的內置 TokenAuthentication 是非常基礎的設置。 因此,它不支持設置令牌過期,這是可以添加的安全性改進。 它還為每個用戶僅生成一個令牌,因此網站上的用戶以及隨後的移動應用程序將使用相同的令牌。 由於有關用戶的信息存儲在本地,因此這可能會導致維護和更新兩組客戶信息的問題。
JSON Web令牌(JWT)是令牌的增強的新版本,可以通過幾個第三方軟件包將其添加到 Django REST Framework 中。 JWT 具有許多優點,包括生成唯一的客戶端令牌和令牌過期的能力。 它們既可以在服務器上生成,也可以使用第三方服務(如Auth0)生成。 而且,JWT 可以被加密,從而使其更安全地通過不安全的 HTTP 連接發送。
最終,對於大多數 Web API 來說,最安全的選擇是使用基於令牌的身份驗證方案。 JWT 是一種不錯的,現代的添加,盡管它們需要其他配置。 因此,在本書中,我們將使用內置的 TokenAuthentication。
第一步是配置我們的新身份驗證設置。 Django REST 框架隨附許多隱式設置的設置。 例如,在我們將DEFAULT_PERMISSION_CLASSES 更新為 IsAuthenticated 之前,已將其設置為 AllowAny。
默認情況下,DEFAULT_AUTHENTICATION_CLASSES 設置為 SessionAuthentication 和 BasicAuthentication。 讓我們將它們明確添加到我們的 blog_project/settings.py
文件中。
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_AUTHENTICATION_CLASSES': [ # new 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ],}
為什麼要同時使用兩種方法? 答案是它們有不同的用途。 會話用於增強 Browsable API 的功能以及登錄和注銷該功能的能力。 BasicAuthentication 用於在 API 本身的 HTTP 標頭中傳遞會話 ID。
如果您在 http://127.0.0.1:8000/api/v1/
上重新浏覽了可浏覽的 API,它將像以前一樣工作。