--- title: Authentication and Authorization tags: Blue 的學習紀錄 --- # Authentication and Authorization :::info **Authentication**: 驗證 檢查身分 確認使用者的身分是否符合使用者所聲稱的身分 **Authorization**: 授權 檢查行為 確認使用者是否擁有權限進行特定操作 ::: 如何做登入的功能? 伺服器如何驗證使用者的身分? 伺服器如何驗證使用者的行為? # state - Stateful 儲存 authorization info 在 server side,已有一段歷史的作法 - Stateless 儲存 authorization info 在 client side,較新的做法,通常會搭配簽署來確保資料沒被修改過 # token 可以將 token 想像成識別證 在 client-server 的架構中,為了讓 server 辨認接收到的 request 身分為何,有各式各樣的方法讓 client 傳送 token 給 server 例如將 token 放在: - URL,例如 `test.com/?token=xxx` - HTTP body - HTTP Header:Cookie - HTTP Header:Authorization token 放在 URL 非常不安全,光是在歷史瀏覽紀錄上就暴露了 token 放在 HTTP body 則是不符合規範,例如 GET 時 body 必須為空,不能有任何東西 放在 cookie 則是一個已經相當成熟的做法 瀏覽器對於 cookie 的支援度極高 在歷史的考驗之下,瀏覽器對於 cookie 的保護已經有一定的安全性 但各品牌瀏覽器對於 cookie 的控管可能會有些微差異,這是需要特別留意的部分 在前端頁面以 AJAX 類的方法,將 token 放在 Header 的 Authorization 進行傳遞 token 的傳遞需要工程師於前端進行管控,而不像是 cookie 由瀏覽器控管 # HTTP Basic Authentication 對 web resources 做 access control 的最簡單作法 是 HTTP 協定內建的方法 當瀏覽器收到的 response header 內含有 `WWW-Authenticate: Basic` 瀏覽器就會向 user 發起 challenge,需要輸入使用者名稱與密碼 輸入後的使用者名稱與密碼會以 Base64 的方式編碼後傳送  > 圖片來源:https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication 因為是 encode 而不是 hash 或 encrypt 所以在 HTTP 的環境下是不安全的  :::info 若 server 的 challenge response 沒有設定 realm 的內容 則在 challenge 上顯示的是 host name 但即使設定了 realm 的內容 在 chrome 仍然只會顯示 host name https://stackoverflow.com/questions/45666306/change-basic-http-authentication-realm-and-login-dialog-message ::: 優點: - 簡單方便 參考資料: - https://en.wikipedia.org/wiki/Basic_access_authentication - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate # Cookie cookie 表示為放在 http header 上的一小段資料,通常 max size 為 4096 bytes 例如可以將登入成功後收到的 token 以 cookie 的方式,在接續的 request 都帶上這份 cookie,server 收到 cookie 便能知道這是已經授權過的 user 所發送的 request,便能回傳需要授權的資料 cookie 的作用範圍: https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Cookies#cookies_%E7%9A%84%E4%BD%9C%E7%94%A8%E7%AF%84%E5%9C%8D 實務上來說,當 server 收到 client 的登入請求,驗證帳號密碼正確後,在 respond header 加上 `Set-Cookie: key=value` client 收到這樣的 respond 後,瀏覽器就會儲存 `key=value` 的資訊。在之後的 request 就會在 header 中加入 `Cookie: key=value` 假如 cookie 的內容含有 `admin=true/false` 的設定,如何確保 user 不會擅自更改設定? 一個直覺的方式就是對 cookie 的內容做加密,加密雖然可以一定程度阻止 cookie 內容被修改,但是會增加伺服器的運算負擔;且加密後的字串通常長度會增加,cookie 的 max size 只有 4096 bytes,加密會限縮 cookie 可以傳遞的內容大小 替代解決方法是 session store,不再將敏感資訊放在 cookie,而是放在資料庫,cookie 的內容則改為存放號碼牌(session ID),server 收到號碼牌後去資料庫讀取資料來得知使用者是誰 流程變成:使用者登入成功後,伺服器建立一組 session data,ID 放在 cookie 傳給 user,與 ID 對應的 data 則存放在資料庫 當收到 request 內的 cookie 含有特定 ID 時,查詢資料庫確認後,便認定這是經過登入後 的 request ID 不能是簡單可預測的數字,例如 `001`、`123` 這類的簡單數字,而應該是一長串難以預測其規則的字串 這樣才能防止惡意使用者猜測他人的 session ID session store 的缺點在於,使用者每當在需要授權的頁面進行任何操作,伺服器都得再去跟 session store 進行 token 的驗證 所以就算是使用 Redis 這種 in-memory 的快速資料庫,只要使用者足夠龐大,session store 的驗證方式將成為效能瓶頸 參考資料: - https://youtu.be/NUGMceeFDnU - https://youtu.be/z0wPnx1bfcQ ## session store 要注意的細節 在這之前,可以先閱讀胡立寫的 [session cookie 介紹文章](https://hulitw.medium.com/session-and-cookie-15e47ed838bc) 裡面以貼近生活的抽象化比喻解釋了一遍 session 和 cookie 到底是什麼;並且再追根究柢的帶領讀者看一遍 RFC 文件上的相關定義;最後以實作來加強講解整個概念 session store 要注意的細節 - session id 必須足夠複雜而難以猜測 - 如何確保 session id 不被竊取? - 每組 session id 必須獨一無二 ## express-session 資料來源: https://github.com/aszx87410/blog/issues/46 套件: https://github.com/expressjs/session express 用於處理 session 的 middleware 範例(取自以上資料來源): ```javascript= const express = require("express"); const session = require("express-session"); const app = express(); const port = 8080; // 使用 session middleware app.use( session({ secret: "keyboard cat", }) ); app.get("/", function (req, res, next) { // 可以用 req.session 拿取存在 session 的值 // 這邊判斷有沒有 req.session.views // 如果有的話就 +1,反之初始化成 1 // 所以 req.session 可讀也可寫 if (req.session.wow) { req.session.wow++; res.write("views: " + req.session.wow); res.end(); } else { req.session.wow = 1; res.end("welcome to the session demo. refresh!"); } }); app.listen(port, () => { console.log(`Example app listening on port ${port}!`); }); ``` 觀察範例後可以發現,這個套件做到的是: - `req.session.wow` 即相當於 `Set-Cookie` 的功能 - cookie 內的 attribute 名稱預設為`connect.sid`,並不是範例裡的`wow` - `connect.sid` 的內容會是一組已經經過簽署(sha256 + secret)及編碼(base64)的 session ID - session data 會暫存在 process 裡,所以伺服器重開後,session data 就會消失 - cookie 的生命只存在於瀏覽器的工作階段結束前 所以要應用於 production 時,還得將其 session data 儲存到資料庫 # Json Web Token 將 JSON 資料經過特殊處理後即可變成 JWT 使用者登入成功後,伺服器回傳 JWT 給使用者 使用者在接下來的 request 將 JWT 放在 HTTP Header 的 `Authorization: Bearer JWT` 若伺服器生成的 JWT 有經過簽署,則伺服器就能單獨驗證使用者傳遞的 JWT,不必經過資料庫 JWT 適用於微服務、API,好處在於不需要中心化的資料庫系統來做驗證,單靠伺服器即可與使用者溝通,解決了 cookie 需要頻繁與 session store 做溝通的缺點 但也因為伺服器沒有對 JWT 管理的手段,除了預設 JWT 的過期時間以外,伺服器無法單方面註銷掉特定 JWT 另外,JWT 的內容也不能存放敏感資訊,因為 JWT 的內容(payload)只是經過 base64 的編碼而已,任何人都能輕易存取儲存在 JWT 內的資訊。JWT 能做到的是確保伺服器收到的 JWT 沒被竄改過,並不能阻止 JWT 被第三方讀取 # Signature Cookie? 相較於 session store,也就是 cookie 內傳 session ID 的作法 JWT 的優點在於: 1. 效率,不用 session store 即可單從 server side 進行驗證 2. 可信,JWT 發出去之前已經經過簽署,所以可以驗證傳回來的 JWT 是否經過修改。也就是說,基本上可以相信任何傳回來的 JWT 而 JWT 的缺點(相較於 session store)在於: 1. 實作麻煩,前端要寫 AJAX 來處理 JWT 的儲存以及發送;cookie 則是瀏覽器自動處理,非常方便 2. 安全性,JWT 極容易被讀取;而 cookie 則擁有 HttpOnly 來防止 js 讀取,以及 SameSite 防止 CSRF 既然如此,為何不對 cookie 也做簽署和傳遞 payload(cookie-based session)? 簽署後的 cookie,不但維持原來的安全性,而且也能確保來源可信 放入 payload 後的 cookie,也一樣不需要 session store 進行驗證 也就是簽署的 cookie 看似集了 JWT 和 cookie 的優點於一身? 為何不這麼做?而是普遍還在使用 JWT? # OAuth 2 <https://developers.google.com/identity/sign-in/web/sign-in> <https://tools.ietf.org/html/rfc6749>
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up