後端互動
我知道洋蔥模型:請求全部進來之後,出去時也會從這裡走一段。
中介軟體的話,用我的理解,有點像是 Python 裡面的 @ 裝飾器。在所有函式被呼叫之前都會先走一遍這裡。如果需要對每一個路由訪問都進行一次的重複操作,那這些操作基本上都要放到中介軟體裡。
關於 Token
一般稱為 JWT。這東西主要是給後端用的,比如鑑權。你不能前端每次發請求過來都去查一遍資料庫,那樣效率太低也很麻煩,IO耗損會很大。
所以會有 Token 機制:
- 前端每次請求都會把 Token 發過來,後端通過它核查登入狀態。
- 如果驗證沒有問題,使用者就不會被跳轉到登入介面。
- Token 中通常包含一些基本資訊,比如: (a) 使用者 ID (b) 許可權資訊 (c) Token 的過期時間
一般來說,對 Token 的校驗都會放在中介軟體裡,每次在路由跳轉之前先校驗許可權,確認使用者是否有權進入頁面。而另一個東西就是 JSON 的返回資料,這是給前端用的(除非要求前端每次都很麻煩地去解析 Token)。前端會藉助裡面的資料,來決定是否要進行下一步渲染等等。
關於密碼的處理
最好是在後端將其轉換為雜湊(Hash),否則會面臨很尷尬的安全問題。如果雜湊是在前端生成的,攻擊者只需要修改一下 JS 程式碼,就可以直接繞過驗證邏輯與資料庫互動並完成登入,這會讓加密行為變得根本沒意義。所以各種安全驗證最好都放在後端(服務端)來處理,因為後端執行在伺服器上,即便有人在瀏覽器前端去下功夫破解,也沒辦法更改後端的邏輯。而後端對前端是基於一個零信任原則的。
關於 JWT Token 在前端的儲存
通常有兩個選擇:直接存到 LocalStorage 裡面、把它封裝成 Cookie。與此對應,後端也需要進行相應處理。之所以將 Cookie 單獨劃分出來,主要原因是 Cookie 的安全係數更高。
如果只是把 Token 存在 LocalStorage 中,只要 JS 一行程式碼就能把它全部讀取出來。在這種情況下,它就沒辦法防範 XSS 攻擊,只要有一個網頁端的 JS 程式碼注入,就能把所有的 Token 扒得一乾二淨,而 Cookie 就可以有效避免這個問題。
而且這裡有一個重要的區分:F12 除錯臺確實可以直接獲得 Cookie,但那是我作為瀏覽器使用者的的最高許可權;而另一個則是直接在網頁端執行的 JS 程式碼——這兩者是完全不一樣的。對於網頁端執行的 JS 程式碼來說,設定了 HttpOnly 的 Cookie 它就是拿不到的,但是對於 LocalStorage 講,網頁端執行的 JS 也可以直接拿到,這個差別很大。
關於路由地址
這個地方遵循相對路徑的邏輯(包括主路由、子路由)。
比如主路由是 /api,那麼所有 /api 的請求都會被轉發到這裡,然後再根據後面的依賴關係轉發到子路由。例如 /api/xx 就會被轉發到 /xx。
關於子路由的寫法:如果要寫子路由的中介軟體,且該中介軟體已經掛載到子路由上了,路徑直接寫一個*即可,在子路由的模組裡面,單獨寫其相對地址:
// dashboard.ts
const dashboardRoute = new Hono()
// 1. 保护这个子路由下的“所有”路径
dashboardRoute.use('/*', jwtMiddleware)
// 2. 定义具体路径 (注意这里不用写 /dashboard 了,只写相对路径)
dashboardRoute.get('/profile', (c) => c.text('我的资料'))
dashboardRoute.get('/settings', (c) => c.text('设置'))
關於這個星*的使用其實挺有意思的:如果是 /* 的話,那一般就是類似萬用字元的存在,在這個路徑後面的內容會全部匹配;但如果只有一個星號的話,那指的就是這個路由裡面的全部匹配。這裡的坑在於,子路由這裡一定要帶 /*。
然後主路由如果只有一個星號的話,那就是主路由之下的全局匹配。如果是在掛載路由組的時候,那一定要把子路由的路徑完整寫下來,這樣才能掛到主路由上,防止錯配:
// app 是主路由,接子路由的完整路径
app.route('/api', authRoute)
app.route('/api/dashboard', dashboardRoute)
關於介面生成和除錯
這方面有比較好的庫可以一次性生成文件。介面搞好後,讓AI寫好註釋,然後再接上Swagger這樣的工具裡,就可以自動產出介面文件了。而且可以線上除錯,自動更新,非常好用。 當然還有另一個選擇,就是一步到位,讓 AI 全部處理。確實,這個工具在今天看來是可有可無的,但如果有一些除錯上的需求,加上它可能會比較好。或者說,甚至連除錯的單元測試也可以交給 AI 來寫,這確實也沒問題。