六種Web驗證方法大揭祕

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文最初發佈於testdriven.io網站,經原作者授權由InfoQ中文站翻譯並分享。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,我們將從一位Python Web開發人員的角度來研究Web端身份驗證領域的一些最常用方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然本文中的代碼示例和資源是供Python開發人員使用的,但是每種身份驗證方法的內容描述都適用於所有Web開發人員。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"身份驗證與授權"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"身份驗證(Authentication)是具備權限的系統驗證嘗試訪問系統的用戶或設備所用憑據的過程。相比之下,授權(Authorization)是給定系統驗證是否允許用戶或設備在系統上執行某些任務的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單地說:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"身份驗證:你是誰?"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"授權:你能做什麼?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"身份驗證先於授權。也就是說,用戶必須先處於合法狀態,然後才能根據其授權級別被授予對資源的訪問權限。驗證用戶身份的最常見方法是用戶名和密碼的組合。用戶通過身份驗證後,系統將爲他們分配不同的角色,例如管理員、主持人等,從而爲他們授予一些特殊的系統權限。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來,我們來​​看一下用於用戶身份驗證的各種方法。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"HTTP基本驗證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP協議中內置的基本身份驗證(Basic auth)是最基本的身份驗證形式。使用它時,登錄憑據隨每個請求一起發送到請求標頭中:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"\"Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=\" your-website.com"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的用戶名和密碼未加密,而是使用一個"},{"type":"codeinline","content":[{"type":"text","text":":"}]},{"type":"text","text":"符號將用戶名和密碼串聯在一起,形成單個字符串:"},{"type":"codeinline","content":[{"type":"text","text":"username:password"}]},{"type":"text","text":",再使用base64編碼這個字符串。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":">>> import base64\n>>>\n>>> auth = \"username:password\"\n>>> auth_bytes = auth.encode('ascii') # convert to bytes\n>>> auth_bytes\nb'username:password'\n>>>\n>>> encoded = base64.b64encode(auth_bytes) # base64 encode\n>>> encoded\nb'dXNlcm5hbWU6cGFzc3dvcmQ='\n>>> base64.b64decode(encoded) # base64 decode\nb'username:password'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方法是無狀態的,因此客戶端必須爲每個請求提供憑據。它適用於API調用以及不需要持久會話的簡單身份驗證工作流。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"未經身份驗證的客戶端請求受限制的資源"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"返回的HTTP401Unauthorized帶有標頭"},{"type":"codeinline","content":[{"type":"text","text":"WWW-Authenticate"}]},{"type":"text","text":",其值爲Basic。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"WWW-Authenticate:Basic"}]},{"type":"text","text":"標頭使瀏覽器顯示用戶名和密碼輸入框"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"輸入你的憑據後,它們隨每個請求一起發送到標頭中:"},{"type":"codeinline","content":[{"type":"text","text":"Authorization: Basic dcdvcmQ="}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b2\/b2c18a95c48e36bc91785691731315c5.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於執行的操作不多,因此使用該方法可以快速完成身份驗證。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易於實現。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有主要瀏覽器均支持。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Base64不是加密。這只是表示數據的另一種方式。由於base64編碼的字符串以純文本格式發送,因此可以輕鬆解碼。這麼差的安全性很容易招致多種類型的攻擊。因此,HTTPS\/SSL是絕對必要的。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"憑據必須隨每個請求一起發送。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只能使用無效的憑據重寫憑據來註銷用戶。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-httpauth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-HTTPAuth"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/hirokiky\/django-basicauth\/","title":"","type":null},"content":[{"type":"text","text":"django-basicauth"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/fastapi.tiangolo.com\/advanced\/security\/http-basic-auth\/","title":"","type":null},"content":[{"type":"text","text":"FastAPI: HTTP BasicAuth"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Flask-HTTP包,可以輕鬆地在Flask中完成基本的HTTP身份驗證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from flask import Flask\nfrom flask_httpauth import HTTPBasicAuth\nfrom werkzeug.security import generate_password_hash, check_password_hash\n\napp = Flask(__name__)\nauth = HTTPBasicAuth()\n\nusers = {\n \"username\": generate_password_hash(\"password\"),\n}\n\n\[email protected]_password\ndef verify_password(username, password):\n if username in users and check_password_hash(users.get(\"username\"), password):\n return username\n\n\[email protected](\"\/\")\[email protected]_required\ndef index():\n return f\"You have successfully logged in, {auth.current_user()}\"\n\n\nif __name__ == \"__main__\":\n app.run()"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/html\/rfc7617","title":"","type":null},"content":[{"type":"text","text":"IETF:“基本”HTTP身份驗證方案"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/blog.miguelgrinberg.com\/post\/restful-authentication-with-flask","title":"","type":null},"content":[{"type":"text","text":"使用Flask進行RESTful身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.django-rest-framework.org\/api-guide\/authentication\/#basicauthentication","title":"","type":null},"content":[{"type":"text","text":"DRF基本身份驗證指南"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/gist.github.com\/nilsdebruin\/8b36cd98c9949a1a87e3a582f70146f1","title":"","type":null},"content":[{"type":"text","text":"FastAPI基本身份驗證示例"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"HTTP摘要驗證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP Digest Auth(或Digest Access Auth)是HTTP基本驗證的一種更安全的形式。主要區別在於HTTP摘要驗證的密碼是以MD5哈希形式代替純文本形式發送的,因此它比基本身份驗證更安全。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"未經身份驗證的客戶端請求受限制的資源"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"服務器生成一個隨機值(稱爲隨機數,nonce),併發回一個HTTP 401未驗證狀態,帶有一個"},{"type":"codeinline","content":[{"type":"text","text":"WWW-Authenticate"}]},{"type":"text","text":"標頭(其值爲"},{"type":"codeinline","content":[{"type":"text","text":"Digest"}]},{"type":"text","text":")以及隨機數:"},{"type":"codeinline","content":[{"type":"text","text":"WWW-Authenticate:Digestnonce=\"44f0437004157342f50f935906ad46fc\""}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"WWW-Authenticate:Basic"}]},{"type":"text","text":"標頭使瀏覽器顯示用戶名和密碼輸入框"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"輸入你的憑據後,系統將對密碼進行哈希處理,然後與每個請求的隨機數一起在標頭中發送:"},{"type":"codeinline","content":[{"type":"text","text":"Authorization: Digest username=\"username\","}]},{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"nonce=\"16e30069e45a7f47b4e2606aeeb7ab62\", response=\"89549b93e13d438cd0946c6d93321c52\""}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"服務器使用用戶名獲取密碼,將其與隨機數一起哈希,然後驗證哈希是否相同"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/5a\/5a67ff3806199c062572ca42c627dd36.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於密碼不是以純文本形式發送的,因此比基本身份驗證更安全。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易於實現。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有主要瀏覽器均支持。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"憑據必須隨每個請求一起發送。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只能使用無效的憑據重寫憑據來註銷用戶。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與基本身份驗證相比,由於無法使用bcrypt,因此密碼在服務器上的安全性較低。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"容易受到中間人攻擊。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-httpauth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-HTTPAuth"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flask-HTTP包也支持摘要HTTP驗證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from flask import Flask\nfrom flask_httpauth import HTTPDigestAuth\n\napp = Flask(__name__)\napp.config[\"SECRET_KEY\"] = \"change me\"\nauth = HTTPDigestAuth()\n\nusers = {\n \"username\": \"password\"\n}\n\n\[email protected]_password\ndef get_user(username):\n if username in users:\n return users.get(username)\n\n\[email protected](\"\/\")\[email protected]_required\ndef index():\n return f\"You have successfully logged in, {auth.current_user()}\"\n\n\nif __name__ == \"__main__\":\n app.run()\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/html\/rfc7616","title":"","type":null},"content":[{"type":"text","text":"IETF:HTTP摘要訪問身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/2.python-requests.org\/en\/latest\/user\/authentication\/#digest-authentication","title":"","type":null},"content":[{"type":"text","text":"來自請求庫的摘要身份驗證"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基於會話的驗證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用基於會話的身份驗證(或稱會話cookie驗證、基於cookie的驗證)時,用戶狀態存儲在服務器上。它不需要用戶在每個請求中提供用戶名或密碼,而是在登錄後由服務器驗證憑據。如果憑據有效,它將生成一個會話,並將其存儲在一個會話存儲中,然後將其會話ID發送回瀏覽器。瀏覽器將這個會話ID存儲爲cookie,該cookie可以在向服務器發出請求時隨時發送。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於會話的身份驗證是有狀態的。每次客戶端請求服務器時,服務器必須將會話放在內存中,以便將會話ID綁定到關聯的用戶。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ce\/ce669158cbb1a345c38e5c29c9948405.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"http會話身份驗證工作流程"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後續登錄速度更快,因爲不需要憑據。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"改善用戶體驗。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相當容易實現。許多框架(例如Django)都是開箱即用的。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它是有狀態的。服務器要在服務端跟蹤每個會話。用於存儲用戶會話信息的會話存儲需要在多個服務之間共享以啓用身份驗證。因此,由於REST是無狀態協議,它不適用於RESTful服務。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即使不需要驗證,Cookie也會隨每個請求一起發送"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易受CSRF攻擊。在"},{"type":"link","attrs":{"href":"https:\/\/testdriven.io\/blog\/csrf-flask\/","title":"","type":null},"content":[{"type":"text","text":"這裏"}]},{"type":"text","text":"閱讀更多關於CSRF以及如何在Flask中防禦它的信息。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-login.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-Login"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-httpauth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-HTTPAuth"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/docs.djangoproject.com\/en\/3.1\/topics\/auth\/","title":"","type":null},"content":[{"type":"text","text":"Django中的用戶身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/MushroomMaula\/fastapi_login","title":"","type":null},"content":[{"type":"text","text":"FastAPI-Login"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/frankie567\/fastapi-users","title":"","type":null},"content":[{"type":"text","text":"FastAPI-Users"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flask-Login非常適合基於會話的身份驗證。這個包負責登錄和註銷,並可以在一段時間內記住用戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from flask import Flask, request\nfrom flask_login import (\n LoginManager,\n UserMixin,\n current_user,\n login_required,\n login_user,\n)\nfrom werkzeug.security import generate_password_hash, check_password_hash\n\n\napp = Flask(__name__)\napp.config.update(\n SECRET_KEY=\"change_this_key\",\n)\n\nlogin_manager = LoginManager()\nlogin_manager.init_app(app)\n\n\nusers = {\n \"username\": generate_password_hash(\"password\"),\n}\n\n\nclass User(UserMixin):\n ...\n\n\n@login_manager.user_loader\ndef user_loader(username: str):\n if username in users:\n user_model = User()\n user_model.id = username\n return user_model\n return None\n\n\[email protected](\"\/login\", methods=[\"POST\"])\ndef login_page():\n data = request.get_json()\n username = data.get(\"username\")\n password = data.get(\"password\")\n\n if username in users:\n if check_password_hash(users.get(username), password):\n user_model = User()\n user_model.id = username\n login_user(user_model)\n else:\n return \"Wrong credentials\"\n return \"logged in\"\n\n\[email protected](\"\/\")\n@login_required\ndef protected():\n return f\"Current user: {current_user.id}\"\n\n\nif __name__ == \"__main__\":\n app.run()\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/id\/draft-broyer-http-cookie-auth-00.html","title":"","type":null},"content":[{"type":"text","text":"IETF:基於Cookie的HTTP身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-add-authentication-to-your-app-with-flask-login","title":"","type":null},"content":[{"type":"text","text":"如何使用Flask-Login向你的應用程序添加身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/testdriven.io\/blog\/flask-spa-auth\/","title":"","type":null},"content":[{"type":"text","text":"Flask針對單頁應用的基於會話的驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/testdriven.io\/blog\/csrf-flask\/","title":"","type":null},"content":[{"type":"text","text":"Flask中的CSRF保護"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/learndjango.com\/tutorials\/django-login-and-logout-tutorial","title":"","type":null},"content":[{"type":"text","text":"Django登錄和註銷教程"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/testdriven.io\/blog\/django-spa-auth\/","title":"","type":null},"content":[{"type":"text","text":"單頁應用的基於Django會話的驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/frankie567.github.io\/fastapi-users\/configuration\/authentication\/cookie\/","title":"","type":null},"content":[{"type":"text","text":"FastAPI-Users:Cookie驗證"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基於令牌的身份驗證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方法使用令牌而不是cookie來驗證用戶。用戶使用有效的憑據驗證身份,服務器返回簽名的令牌。這個令牌可用於後續請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最常用的令牌是JSON Web Token(JWT)。JWT包含三個部分:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標頭(包括令牌類型和使用的哈希算法)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"負載(包括聲明,是關於主題的陳述)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簽名(用於驗證消息在此過程中未被更改)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這三部分都是base64編碼的,並使用一個"},{"type":"codeinline","content":[{"type":"text","text":"."}]},{"type":"text","text":"串聯並做哈希。由於它們已編碼,因此任何人都可以解碼和讀取消息。但是,只有驗證的用戶才能生成有效的簽名令牌。令牌使用簽名來驗證,簽名用的是一個私鑰。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JSON Web Token(JWT)是一種緊湊的、URL安全的方法,用於表示要在兩方之間轉移的聲明。JWT中的聲明被編碼爲一個JSON對象,用作一個JSON Web Signature(JWS)結構的負載,或一個JSON Web Encryption(JWE)結構的純文本,從而使聲明可以進行數字簽名,或使用一個消息驗證碼Message Authentication Code(MAC)來做完整性保護和\/或加密。——IETF"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"令牌不必保存在服務端。只需使用它們的簽名即可驗證它們。近年來,由於RESTfulAPI和單頁應用(SPA)的出現,令牌的使用量有所增加。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ee\/ee9309ff90d656b5b218eb0ff8c41349.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"令牌驗證工作流程"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它是無狀態的。服務器不需要存儲令牌,因爲可以使用簽名對其進行驗證。由於不需要數據庫查找,因此可以讓請求更快。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"適用於微服務架構,其中有多個服務需要驗證。我們只需在每一端配置如何處理令牌和令牌密鑰即可。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據令牌在客戶端上的保存方式,它可能導致XSS(通過localStorage)或CSRF(通過cookie)攻擊。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"令牌無法被刪除。它們只能過期。這意味着如果令牌泄漏,則攻擊者可以濫用令牌直到其到期。因此,將令牌過期時間設置爲非常小的值(例如15分鐘)是非常重要的。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要設置令牌刷新以在到期時自動發行令牌。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刪除令牌的一種方法是創建一個將令牌列入黑名單的數據庫。這爲微服務架構增加了額外的開銷並引入了狀態。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/vimalloc\/flask-jwt-extended","title":"","type":null},"content":[{"type":"text","text":"Flask-JWT-Extended"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-httpauth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-HTTPAuth"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/SimpleJWT\/django-rest-framework-simplejwt","title":"","type":null},"content":[{"type":"text","text":"適用於DjangoREST框架的簡單JWT"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/IndominusByte\/fastapi-jwt-auth","title":"","type":null},"content":[{"type":"text","text":"FastAPIJWTAuth"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flask-JWT-Extended包爲處理JWT提供了很多可能性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from flask import Flask, request, jsonify\nfrom flask_jwt_extended import (\n JWTManager,\n jwt_required,\n create_access_token,\n get_jwt_identity,\n)\nfrom werkzeug.security import check_password_hash, generate_password_hash\n\napp = Flask(__name__)\napp.config.update(\n JWT_SECRET_KEY=\"please_change_this\",\n)\n\njwt = JWTManager(app)\n\nusers = {\n \"username\": generate_password_hash(\"password\"),\n}\n\n\[email protected](\"\/login\", methods=[\"POST\"])\ndef login_page():\n username = request.json.get(\"username\")\n password = request.json.get(\"password\")\n\n if username in users:\n if check_password_hash(users.get(username), password):\n access_token = create_access_token(identity=username)\n return jsonify(access_token=access_token), 200\n\n return \"Wrong credentials\", 400\n\n\[email protected](\"\/\")\n@jwt_required\ndef protected():\n return jsonify(logged_in_as=get_jwt_identity()), 200\n\n\nif __name__ == \"__main__\":\n app.run()\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/jwt.io\/introduction","title":"","type":null},"content":[{"type":"text","text":"JSONWeb令牌簡介"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/html\/rfc7519","title":"","type":null},"content":[{"type":"text","text":"IETF:JSONWebToken(JWT)"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/simpleisbetterthancomplex.com\/tutorial\/2018\/12\/19\/how-to-use-jwt-authentication-with-django-rest-framework.html","title":"","type":null},"content":[{"type":"text","text":"如何在DjangoREST框架中使用JWT身份驗證"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/testdriven.io\/blog\/fastapi-jwt-auth\/","title":"","type":null},"content":[{"type":"text","text":"使用基於JWT令牌的身份驗證保護FastAPI"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/blog.asayer.io\/jwt-authentication-best-practices","title":"","type":null},"content":[{"type":"text","text":"JWT身份驗證最佳實踐"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一次性密碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一次性密碼(One Time Password,OTP)通常用作身份驗證的確認。OTP是隨機生成的代碼,可用於驗證用戶是否是他們聲稱的身份。它通常用在啓用雙因素身份驗證的應用中,在用戶憑據確認後使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要使用OTP,必須存在一個受信任的系統。這個受信任的系統可以是經過驗證的電子郵件或手機號碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現代OTP是無狀態的。可以使用多種方法來驗證它們。儘管有幾種不同類型的OTP,但基於時間的OTP(TOTP)可以說是最常見的類型。它們生成後會在一段時間後過期。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於OTP讓你獲得了額外的一層安全保護,因此建議將OTP用於涉及高度敏感數據的應用,例如在線銀行和其他金融服務。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現OTP的傳統方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端發送用戶名和密碼"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過憑據驗證後,服務器會生成一個隨機代碼,將其存儲在服務端,然後將代碼發送到受信任的系統"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶在受信任的系統上獲取代碼,然後在Web應用上重新輸入它"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務器對照存儲的代碼驗證輸入的代碼,並相應地授予訪問權限"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TOTP如何工作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端發送用戶名和密碼"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過憑據驗證後,服務器會使用隨機生成的種子生成隨機代碼,並將種子存儲在服務端,然後將代碼發送到受信任的系統"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶在受信任的系統上獲取代碼,然後將其輸入回Web應用"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務器使用存儲的種子驗證代碼,確保其未過期,並相應地授予訪問權限"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"谷歌身份驗證器、微軟身份驗證器和FreeOTP等OTP代理如何工作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註冊雙因素身份驗證(2FA)後,服務器會生成一個隨機種子值,並將該種子以唯一QR碼的形式發送給用戶"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶使用其2FA應用程序掃描QR碼以驗證受信任的設備"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每當需要OTP時,用戶都會在其設備上檢查代碼,然後在Web應用中輸入該代碼"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務器驗證代碼並相應地授予訪問權限"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"添加了一層額外的保護"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不會有被盜密碼在實現OTP的多個站點或服務上通過驗證的危險"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你需要存儲用於生成OTP的種子。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"像谷歌驗證器這樣的OTP代理中,如果你丟失了恢復代碼,則很難再次設置OTP代理"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當受信任的設備不可用時(電池耗盡,網絡錯誤等)會出現問題。因此通常需要一個備用設備,這個設備會引入一個額外的攻擊媒介。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/pyauth.github.io\/pyotp\/","title":"","type":null},"content":[{"type":"text","text":"PyOTP——Python一次性密碼庫"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/django-otp\/django-otp","title":"","type":null},"content":[{"type":"text","text":"django-otp"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PyOTP包提供了基於時間和基於計數器的OTP。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from time import sleep\n\nimport pyotp\n\nif __name__ == \"__main__\":\n otp = pyotp.TOTP(pyotp.random_base32())\n code = otp.now()\n print(f\"OTP generated: {code}\")\n print(f\"Verify OTP: {otp.verify(code)}\")\n sleep(30)\n print(f\"Verify after 30s: {otp.verify(code)}\")\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"OTP generated: 474771\nVerify OTP: True\nVerify after 30s: False\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/html\/rfc6238","title":"","type":null},"content":[{"type":"text","text":"IETF:TOTP:基於時間的一次性密碼算法"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/tools.ietf.org\/html\/rfc2289","title":"","type":null},"content":[{"type":"text","text":"IETF:一次性密碼系統"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/hackernoon.com\/implementing-2fa-how-time-based-one-time-password-actually-works-with-python-examples-cm1m3ywt","title":"","type":null},"content":[{"type":"text","text":"實現2FA:基於時間的一次性密碼現實中是如何工作的(使用Python示例)"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"OAuth和OpenID"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"OAuth\/OAuth2和OpenID分別是授權和身份驗證的流行形式。它們用於實現社交登錄,一種單點登錄(SSO)形式。社交登錄使用來自諸如Facebook、Twitter或谷歌等社交網絡服務的現有信息登錄到第三方網站,而不是創建一個專用於該網站的新登錄帳戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當你需要高度安全的身份驗證時,可以使用這種身份驗證和授權方法。這些提供者中有一些擁有足夠的資源來增強身份驗證能力。利用經過反覆考驗的身份驗證系統,可以讓你的應用程序更加安全。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方法通常與基於會話的身份驗證結合使用。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你訪問的網站需要登錄。你轉到登錄頁面,然後看到一個名爲“使用谷歌登錄”的按鈕。單擊該按鈕,它將帶你到谷歌登錄頁面。通過身份驗證後,你將被重定向回自動登錄的網站。這是使用OpenID進行身份驗證的示例。它讓你可以使用現有帳戶(通過一個OpenID提供程序)進行身份驗證,而無需創建新帳戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最著名的OpenID提供方有谷歌、Facebook、Twitter和GitHub。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"登錄後,你可以轉到網站上的下載服務,該服務可讓你直接將大文件下載到谷歌雲端硬盤。網站如何訪問你的Google雲端硬盤?這裏就會用到OAuth。你可以授予訪問另一個網站上資源的權限。在這裏,你授予的就是寫入谷歌雲端硬盤的訪問權限。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提高安全性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於無需創建和記住用戶名或密碼,因此登錄流程更加輕鬆快捷。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果發生安全漏洞,由於身份驗證是無密碼的,因此不會對第三方造成損害。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"缺點"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,你的應用程序依賴於你無法控制的另一個應用。如果OpenID系統關閉,則用戶將無法登錄。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"人們通常傾向於忽略OAuth應用程序請求的權限。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在你配置的OpenID提供方上沒有帳戶的用戶將無法訪問你的應用程序。最好的方法是同時實現多種途徑。例如用戶名和密碼以及OpenID,並讓用戶自行選擇。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想要實現社交登錄?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/authomatic.github.io\/authomatic\/","title":"","type":null},"content":[{"type":"text","text":"Authomatic"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/python-social-auth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Python Social Auth"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-dance.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-Dance"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/django-allauth.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"django-allauth"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想要運行自己的OAuth或OpenID服務?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/authlib.org\/","title":"","type":null},"content":[{"type":"text","text":"Authlib"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/oauthlib.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"OAuthLib"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/flask-oauthlib.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Flask-OAuthlib"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/django-oauth-toolkit.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Django OAuth Toolkit"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/django-oidc-provider.readthedocs.io\/","title":"","type":null},"content":[{"type":"text","text":"Django OIDC Provider"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/fastapi.tiangolo.com\/tutorial\/security\/simple-oauth2\/","title":"","type":null},"content":[{"type":"text","text":"FastAPI:帶有密碼和Bearer的簡單OAuth2"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/fastapi.tiangolo.com\/tutorial\/security\/oauth2-jwt\/#advanced-usage-with-scopes","title":"","type":null},"content":[{"type":"text","text":"FastAPI:帶密碼(和哈希)的OAuth2,帶JWT令牌的Bearer"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以使用Flask-Dance實現GitHub社交身份驗證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"from flask import Flask, url_for, redirect\nfrom flask_dance.contrib.github import make_github_blueprint, github\n\napp = Flask(__name__)\napp.secret_key = \"change me\"\napp.config[\"GITHUB_OAUTH_CLIENT_ID\"] = \"1aaf1bf583d5e425dc8b\"\napp.config[\"GITHUB_OAUTH_CLIENT_SECRET\"] = \"dee0c5bc7e0acfb71791b21ca459c008be992d7c\"\n\ngithub_blueprint = make_github_blueprint()\napp.register_blueprint(github_blueprint, url_prefix=\"\/login\")\n\n\[email protected](\"\/\")\ndef index():\n if not github.authorized:\n return redirect(url_for(\"github.login\"))\n resp = github.get(\"\/user\")\n assert resp.ok\n return f\"You have successfully logged in, {resp.json()['login']}\"\n\n\nif __name__ == \"__main__\":\n app.run()\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"資源"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/developer.okta.com\/blog\/2019\/10\/21\/illustrated-guide-to-oauth-and-oidc","title":"","type":null},"content":[{"type":"text","text":"OAuth和OpenIDConnect圖解指南"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/mherman.org\/presentations\/node-oauth-openid\/#1","title":"","type":null},"content":[{"type":"text","text":"OAuth2.0和OpenIDConnect簡介"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/realpython.com\/flask-google-login\/","title":"","type":null},"content":[{"type":"text","text":"使用谷歌登錄創建一個Flask應用程序"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/learndjango.com\/tutorials\/django-allauth-tutorial","title":"","type":null},"content":[{"type":"text","text":"Django-allauth教程"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/medium.com\/data-rebels\/fastapi-google-as-an-external-authentication-provider-3a527672cf33","title":"","type":null},"content":[{"type":"text","text":"FastAPI——使用谷歌作爲外部身份驗證提供方"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,我們研究了許多不同的Web身份驗證方法,它們都有各自的優缺點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你什麼時候應該使用哪種方法?具體情況要具體分析。一些基本的經驗法則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"對於利用服務端模板的Web應用程序,通過用戶名和密碼進行基於會話的身份驗證通常是最合適的。你也可以添加OAuth和OpenID。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"對於RESTful API,建議使用基於令牌的身份驗證,因爲它是無狀態的。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果必須處理高度敏感的數據,則你可能需要將OTP添加到身份驗證流中。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後請記住,本文的示例僅僅是簡單的演示。生產環境需要進一步的配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/testdriven.io\/blog\/web-authentication-methods\/#session-based-auth"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章