2021 年 02 月 === ## 蒼時 ## Middleware 可以參考維基百科的[中介軟體](https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BB%B6)說明,不過還是很抽象。 簡單來說我們可以理解成在軟體之間連接溝通時用來輔助的程式,像是瀏覽器對伺服器發送請求後,在直接被網站本身處理前可以先透過 Middleware 進行一些處理來方便後面的網站本身取用原始資料就是一種使用情境。 基於 Warden 的 Devise 就有依靠 Warden 提供的 Middleware 來預先處理一些跟登入認證相關的機制,而 Devise 則專注在使用者管理上的邏輯。 > 因為我們比較常接觸 Web 相關的開發所以知道的 Middleware 應用大多是 Web 相關的,這部分像是 Node.js / Golang / PHP 等語言都可以看到 ### Rack 中的 Middleware Rack 的 Middleware 會將 `@app` 物件傳入,並且在 HTTP 請求時依序呼叫 `#call` 方法,當我們處理完畢後則需要呼叫 `@app` 的 `#call` 方法將後續處理轉接給 Application 本身。 ```ruby class StopHereMiddleware def initialize(app, options = {}) @app = app @options = options end def call(env) return [200, {}, ["Stop here"]] # Never run this @app.call(env) end end ``` ## Warden 在 GitHub 上的說明是「標準 Rack 認證框架」也就是專注在處理「Authentication」上的框架,通常要一起看 [AAA 協議](https://en.wikipedia.org/wiki/AAA_(computer_security))來討論,裡面包含「認證」、「授權」、「紀錄」三個部分,而 Warden 負責的是「認證」的部分,也就是「驗證是否存在」的功能。 Warden 的概念算是相當的單純,透過定義各種策略(Strategies)來決定要怎麼去驗證一個使用者,可以是很簡單的帳號密碼或者一組 Token 都可以。 其中 Warden 定義了這幾種概念 * Strategies - 驗證使用者的方式 * Failures - 驗證失敗要採取的行動 * Users - 一個可以辨識使用者的 Key * Callbacks - 不同處理階段的額外處理 * Scopes - 用於區分不同類型的登入使用者 * Session - Warden 儲存在 Session 的資訊(跟使用者相關) ### Warden 的 Middleware 一般來說我們從 `#call` 方法下去觀察就可以知道一個 Middleware 的處理模式。 ```ruby def call(env) # :nodoc: return @app.call(env) if env['warden'] && env['warden'].manager != self env['warden'] = Proxy.new(env, self) result = catch(:warden) do env['warden'].on_request @app.call(env) end result ||= {} case result when Array handle_chain_result(result.first, result, env) when Hash process_unauthenticated(env, result) when Rack::Response handle_chain_result(result.status, result, env) end end ``` 在這邊 Warden 做了以下處理: 1. 如果 Warden Manager 已經存在則不處理 2. 建立 Warden Manager 並且捕捉 `:warden` 事件 3. 呼叫原本的 Application 繼續執行 4. 根據 `result` 回傳的資訊進行處理 這邊 `env['warden'].on_request` 則會去依照目前的登入策略和設定選擇對應的策略物件呼叫,如果發生錯誤時則會拋出 `:warden` 並且讓 `@app.call` 不會被呼叫到,進而用錯誤訊息取代原本的行為回傳。 ## Devise 在 Devise 之中是直接使用 Warden 的 Middleware 進行處理,不過加入了對應的策略來實作 Devise 對登入的處理。 ```ruby # https://github.com/heartcombo/devise/blob/master/lib/devise/rails.rb#L10 # Initialize Warden and copy its configurations. config.app_middleware.use Warden::Manager do |config| Devise.warden_config = config end ``` 剩下的部分直接參考 Devise 的策略實作即可。 ## Cindy https://hackmd.io/@cindyliu923/B1UHtDKZ_