{"id":20619771,"url":"https://github.com/twtrubiks/django_jwt_tutorial","last_synced_at":"2025-04-15T12:12:28.409Z","repository":{"id":48174070,"uuid":"132420743","full_name":"twtrubiks/django_jwt_tutorial","owner":"twtrubiks","description":"認識 JWT 以及透過 Django 實戰 📝","archived":false,"fork":false,"pushed_at":"2024-05-23T14:07:37.000Z","size":34,"stargazers_count":64,"open_issues_count":1,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T12:12:00.617Z","etag":null,"topics":["authenticated","authentication","django","json-web-token","jwt","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-07T07:05:21.000Z","updated_at":"2025-04-01T01:50:31.000Z","dependencies_parsed_at":"2024-11-20T11:02:44.966Z","dependency_job_id":null,"html_url":"https://github.com/twtrubiks/django_jwt_tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_jwt_tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_jwt_tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_jwt_tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango_jwt_tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/django_jwt_tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249067779,"owners_count":21207396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authenticated","authentication","django","json-web-token","jwt","tutorial"],"created_at":"2024-11-16T12:12:29.045Z","updated_at":"2025-04-15T12:12:28.391Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","readme":"# django-jwt-tutorial\n\n認識 JWT 以及透過 Django 實戰 📝\n\n* [Youtube Tutorial Part1 - JWT tutorial](https://youtu.be/p4uWTwkGtZk)\n* [Youtube Tutorial Part2 - django-rest-framework-jwt tutorial](https://youtu.be/CJOysCNAf4s)\n* [Youtube Tutorial Part3 - django + jwt tutorial](https://youtu.be/I_vXGjf8t88)\n\n## 前言\n\n在設計 API 時，通常會有授權以及驗證，而現在很多設計又都是前後端分離，所以，讓我們來了解一下什麼是 JWT :smirk:\n\n本篇文章會介紹 [djangorestframework-simplejwt](https://github.com/jazzband/djangorestframework-simplejwt) 這個套件，以及說明 JWT 原理，最後是簡單的實戰。\n\n在開始介紹之前，先讓我們來了解 authentication 以及 authorization 之間的差異。\n\n### authentication VS authorization\n\n這兩個有甚麼差別呢 ?\n\n先來看看參考 Django 官網對 [auth](https://docs.djangoproject.com/en/2.0/topics/auth/) 的介紹，\n\n原文如下\n\n```text\nThe Django authentication system handles both authentication and authorization.\nauthentication verifies a user is who they claim to be,\nauthorization determines what an authenticated user is allowed to do.\n```\n\n舉個例子，假設小明今天輸入帳號密碼成功登入一個網站，這個行為就稱為 **authentication**，\n\n而登入之後，可能小明擁有管理員的身分進行刪除文章，這個行為就叫做 **authorization**。\n\n換個說法， **authentication** 是確認是否真的有這個人，而 **authorization** 則是這個人是否有\n\n權限做一些事情。\n\n## JWT 介紹\n\n詳細的說明非常建議大家閱讀官方的 [https://jwt.io/introduction/](https://jwt.io/introduction/) 文件，我會挑一些重點出來說明。\n\n### 什麼是 JWT\n\nJWT 全名為 JSON Web Token，是一種公開的標準 ( RFC 7519 )，這種標準定義了 compact 以及 self-contained 的方法，\n\n使用JSON 的形式在各方之間安全的傳遞。\n\n剛剛提到了 compact 以及 self-contained ，這邊說明一下 :relaxed:\n\n**Compact** : 因為 JWT 他們的 size 比較小，所以可以透過 URL POST 參數的形式或是加在一個 HTTP header 裡，\n\n此外，size小代表傳輸越快。\n\n**Self-contained** : payload 裡面包含了使用者的資訊，也就是說解析後就可以看到，不需要再去 query 你的 database。\n\n### 什麼時候應該使用 JWT\n\n最普遍的情境就是 Authentication。\n\n使用者一次性的登入成功後，後續的每一個 request 都包含了 JWT，允許使用者瀏覽 routess, services, resources 。\n\nSingle Sign On ( SSO ) ，又稱單一登入或單點登入，是目前廣泛使用 JWT 的一項功能，因為它的開銷很小，而且\n\n可以很輕鬆的跨 domains :thumbsup:\n\n### JWT 結構\n\n在 compact form 中，JWT 由三個 `.` 所組成，分別為 `Header`，`Payload`，`Signature`。\n\n所以說，一般的 JWT 格式看起來會像是這樣，`xxxxx.yyyyy.zzzzz`。\n\n`Header`\n\n這部分通常包含兩個部分，JWT 以及  hashing algorithm。\n\n```json\n{\n  \"typ\": \"JWT\",\n  \"alg\": \"HS256\"\n}\n```\n\n這個部分是屬於整個 JWT 的第一個部分 ( 經過 Base64Url encoded ) 。\n\n`Payload`\n\n這部分包含了 claims，claims 通常指的是一個實體 ( 一般來說就 user ) 以及一些額外的資訊，有三種 claims，\n\n分別為 `Registered claims`，`Public claims`，`Private claims`。\n\n`Registered claims`\n\n它是一個預先定義的 claims，沒有強制性 ( 但建議使用 )，提供一些實用的內容，如下，\n\n* \"iss\" (Issuer) Claim\n\n* \"sub\" (Subject) Claim\n\n* \"aud\" (Audience) Claim\n\n* \"exp\" (Expiration Time) Claim\n\n當然不只上面所提到的這些，\n\n這邊你可能會問，為什麼都只有三個英文字母做代表  :question:\n\n還記得前面提到的 compact 嗎 :question: 這就是原因 ( size 能小點就小一點 )。\n\n`Public claims`\n\n這些是由使用 JWT 的人下去定義的，但避免使用到已定義的名稱 ( 造成衝突 )，所以應該被定義在\n\n[IANA JSON Web Token Registry](https://www.iana.org/assignments/jwt/jwt.xhtml) 中，或是多使用一些額外的名稱避免衝突。\n\n`Private claims`\n\nThese are the custom claims created to share information between parties that agree on using them and are neither registered or public claims.\n\n底下是一個範例的 payload，\n\n```json\n{\n  \"sub\": \"1234567890\",\n  \"name\": \"John Doe\",\n  \"admin\": true\n}\n```\n\n這個部分是屬於整個 JWT 的第二個部分 ( 經過 Base64Url encoded ) 。\n\n***這邊要注意一點，JWT 裡面的資訊，基本上任何人都可以閱讀 ( 除非你有額外加密 )，否則不要放重要的資訊在你的 JWT 裡面。***\n\n`Signature`\n\n要創造一個 signature，你需要有 encoded header，encoded payload，a secret ( 以 Django 來說，通常是你在\n\n`settings.py` 裡面的 `SECRET_KEY` )，the algorithm specified in the header。\n\n 假如我們使用  HMAC SHA256 algorithm ，這個 signature 創造的方式如下\n\n```text\nHMACSHA256(base64UrlEncode(header) + \".\" +base64UrlEncode(payload),secret)\n```\n\n這個部分是屬於整個 JWT 的第三個部分 ( 經過 Base64Url encoded ) 。\n\n介紹完 JWT 的三個部分之後，我們把在合在一起來看一遍，以下舉例，\n\n```token\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InR3dHJ1YmlrcyIsImV4cCI6MTUyNTY2MjcxNSwiZW1haWwiOiJ0d3RydWJpa3NAZ21haWwuY29tIiwib3JpZ19pYXQiOjE1MjU2NjI0MTV9.jMqH_jKv5nJuxQ7whpOE5kMyekxTqzsDw8iaibS9Cyo\n```\n\n可以把它貼到 [jwt.io Debugger](http://jwt.io/) 中觀看，如下圖，\n\n![alt tag](https://i.imgur.com/m41314q.png)\n\n這邊特別說明一下紅色框起來的 `your-256-bit-secret` 這個，基本上這個 secret 就是你在 `settings.py` 中的 `SECRET_KEY`。\n\n### JWT 如何運作的呢\n\n在 authentication 中，當使用者成功的登入後，server 會回傳一個 JWT 給前端，通常這個 JWT 會在前端被儲存起來，\n\n一般來說是存在 local storage 中，但也可以存在 cookies 中。\n\nJWT 的方法和傳統在 server 中創造一個 session 然後回傳 cookie 的方法不同。\n\nJWT 儲存的方式也各有優缺點，詳細請參考 [Where to Store Tokens](https://auth0.com/docs/security/store-tokens)。\n\n每當使用者想要 access 受保護的 route or resource 時，使用者都必須帶上 JWT，一般  Authorization header 是使用\n\nBearer schema，header 的內容看起來如下，\n\n```header\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n這是一個無狀態的 authentication 機制，因為使用者的狀態絕對不會保存在 server 的 memory 中。\n\nserver 保護的 routes 會去檢查  Authorization header 是否為一個有效的 JWT，如果是，將會允許使用者\n\naccess 受保護的 resources。\n\n如同前面所說的 self-contained，全部所需要的資訊都在 JWT 中，可以降低需要 query database 的次數。\n\n它允許你可以完全的依賴無狀態的 data APIs ，甚至不需要考慮是正在服務哪個 domains 底下的 API，因\n\n為它不使用 cookies。\n\n文件上有提到 Cross-Origin Resource Sharing ( CORS ) ，但經過討論，認為指是強調不使用 cookies，所以\n\n沒有 domains 的問題而已。 ( 所以不要一直執著在 CORS 上面，不然你一定會覺得超怪 :confused: )\n\n如果不了解什麼是 CORS，可參考我之前的文章 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial)。\n\n下面來看一張官網的 JWT 瀏覽器以及 server 之間互動的流程圖，\n\n![alt tag](https://i.imgur.com/F0ucQwQ.png)\n\n圖片來源 [https://jwt.io/introduction/](https://jwt.io/introduction/)。\n\n可以簡單的把瀏覽器想成前端，而將 server 想成後端，這樣就適合用在前後端分離的地方了。\n\n這邊還是要再次提早大家，因為 JWT 使用者是可以讀內容的 ( 簡單的 base64 編碼)，所以記得\n\n不要將重要的資訊放在 JWT 中。\n\n### Why should we use JSON Web Tokens\n\n文件中的最後一部分是介紹 JSON Web Tokens ( JWT )  和 Simple Web Tokens (SWT) 以及\n\nSecurity Assertion Markup Language Tokens (SAML) 比較。\n\n這部分我就不翻譯了，簡單來說，就是 JWT 比較優，size 小，可讀性強以及安全性的分析。\n\n呼~  我終於把大部分的重點都翻譯完了 (有些是依照自己的理解加加減減的翻譯 ) :satisfied:\n\n### Token 已經發出去了, 該怎麼讓它失效 :question:\n\n像是使用者改密碼, 要如何讓舊的 REFRESH TOKEN 失效呢 :question:\n\n(Access Token 就沒辦法了, 必須讓它時間到自己過期, 所以通常 Access Token 時間都很短)\n\n這時候要搭配 [Blacklist app](https://django-rest-framework-simplejwt.readthedocs.io/en/latest/blacklist_app.html#),\n\n當使用者改密碼的時候, 把 refresh token 加入 `token.blacklist()` 黑白單中,\n\n這樣下一次, 當使用這個 refresh token 時, 發現這個 refresh token 在黑名單中,\n\n就可以強制 user 要重新取 token 了, 類似的 response 如下.\n\n```json\n{\n  \"detail\": \"Token is blacklisted\",\n  \"code\": \"token_not_valid\"\n}\n```\n\nServer 端, DB, 只保存 Refresh Token.\n\nClient 端, 會保存 Access Token 和 Refresh Token.\n\n### Sliding tokens\n\n本篇的例子都是 Access Token, 還有一種是 Sliding tokens,\n\n文件可參考 [Token types](https://django-rest-framework-simplejwt.readthedocs.io/en/latest/token_types.html),\n\nSliding tokens 主要是讓使用者方便一點, 但必須要犧牲一點安全性, 如果搭配 Blacklist app 效能會比較低.\n\n## 把玩 djangorestframework-simplejwt\n\n請參考官方文件 [djangorestframework-simplejwt](https://github.com/jazzband/djangorestframework-simplejwt)，或是直接看我的影片說明:relaxed:\n\n* [Youtube Tutorial Part2 - django-rest-framework-jwt tutorial](https://youtu.be/CJOysCNAf4s)\n\n由於這邊會使用到 django-rest-framework 的觀念，所以說你如果不熟悉，可參考我之前的文章\n\n* [Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide](https://github.com/twtrubiks/django-rest-framework-tutorial)\n\n同場加映  [djoser](https://github.com/sunscrapers/djoser)，這個套件整合 django 的 authentication system，提供一系列的\n\nDjango Rest Framework ( DRF ) view 去 handle 基本的  registration, login, logout,\n\npassword reset and account activation 等等。\n\n在實戰中，我只有使用到 djoser 的 create user 的功能，因為 JWT 本身是沒有建立新的 user 的功能。\n\n## 實戰\n\n既然都說明那麼多理論了，當然要來簡單實戰一下，我們就用 Django 模擬前後端分離\n\n( 為什麼說模擬，因為我實在沒學前端的框架 )。\n\n請直接瀏覽 [http://127.0.0.1:8000/account/](http://127.0.0.1:8000/account/)。\n\n簡單的畫面登入，如果未登入 ( 無法取得資料 )，\n\n![alt tag](https://i.imgur.com/8WWJpZ9.png)\n\n紅色的部分是我去 call 一個受保護 API 的資源，因為 header 沒帶上 JWT，所以無法取得資料。\n\n可以自行註冊一個帳號，會是使用我的 twtrubiks/password123，\n\n成功登入後，取的 JWT，我會將它存在 localStorage 中，\n\n![alt tag](https://i.imgur.com/zHpI5O0.png)\n\n接著自然可以取得受保護 API 的資源 ( 因  header 有帶上 JWT，且這個 JWT 是有效的 )\n\n![alt tag](https://i.imgur.com/vbot6Ve.png)\n\n每當進入 [http://127.0.0.1:8000/account/](http://127.0.0.1:8000/account/) 時，我都會先發一個  `/api/token/refresh/` 去 refresh token，\n\n並且將這個 token 存在 localStorage 中 ( 覆蓋掉既有存在 localStore 中的 token )，而這個 token 時效\n\n只有 5 分鐘，也就是說假如你持續 5 分鐘沒在網頁上操作 ( 這邊就只能重新整理頁面模擬 )，你的\n\ntoken 將會過期，並且看到下面這個畫面，因為你的 token 已經失效了。\n\n![alt tag](https://i.imgur.com/8WWJpZ9.png)\n\n## 其他\n\n符合 RFC 規範, 可參考 [PR2](https://github.com/twtrubiks/django_jwt_tutorial/pull/2) - 感謝 NatLee\n\n為了維持教學文的簡單性, 暫時不 merge, 有興趣的可以到連結內觀看.\n\n## 執行環境\n\n* Python 3.8\n\n## Reference\n\n* [djangorestframework-simplejwt](https://github.com/jazzband/djangorestframework-simplejwt)\n* [djoser](https://github.com/sunscrapers/djoser)\n\n## Donation\n\n文章都是我自己研究內化後原創，如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\n\n![alt tag](https://i.imgur.com/LRct9xa.png)\n\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\n\n## License\n\nMIT licens\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango_jwt_tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fdjango_jwt_tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango_jwt_tutorial/lists"}