2008年11月10日 星期一

Merb-Auth (2) 來龍去脈

上一篇我們快速生了一個有 login 功能的 merb app。這一篇要來好解釋一下這些東西的來龍去脈,還有各種可以設定的地方。

首先看看有哪些設定檔。(這裡可能講的有些零亂,不過先看過去,等把下面流程的部份對照一下就應該蠻清楚了。)

在 config/dependencies.rb 可以看到下面這三行:
dependency "merb-auth-core", merb_gems_version
dependency "merb-auth-more", merb_gems_version
dependency "merb-auth-slice-password", merb_gems_version
Merb-Auth 和 Merb 一樣,依據必要性的不同,分成三個部份。
  • merb-auth-core 包含了最基本的功能,像是上一篇講到的 session.user 或是 ensure_authenticated 這些做登入一定會用到的東西。
  • merb-auth-more 包含了一些好用的功能,像是已經寫好的三種登入方式(http-basic / password / openid),登入完可以導回原本要去的頁面的 redirect_back_or 等等。
  • merb-auth-slice-password 則是一個 slice 。slice 是 merb 的一個超強大功能,可以把他想成是一個小型的 app,讓你可以隨意的整合進其它的 app 裡面。我們上一篇,在我們還沒有寫 login form 的 view (app/views/exceptions/unauthenticated.html.erb) 時 ,用的就是這個 slice 已經幫我們寫好的!
預設是這三樣東西通通都啟用。如果你不想用某個部份,可以直接在這裡把他們 comment 掉。

接下來另一個重要的設定檔是 merb/merb-auth/setup.rb:
# Merb::Plugins.config[:"merb-auth"][:login_param]    = :email 
# Merb::Plugins.config[:"merb-auth"][:password_param] = :my_password_field_name
這兩行原本是被 comment 起來。它們是用來設定登入時所用的欄位名稱。預設是用 login/password,不過大家應該比較喜歡 username/password 吧!
Merb::Authentication.user_class = User
這行設定的是登入物件的 class 。所謂的登入物件就是你要用什麼物件來代表登入的這個人。像有人會喜歡用 Person / Account 之類的。
require 'merb-auth-more/mixins/salted_user'
Merb::Authentication.user_class.class_eval{ include Merb::Authentication::Mixins::SaltedUser }
這兩行將 salted password 的邏輯加到了剛剛設定的登入物件 class 裡面。如果你去翻一下上一篇我們的程式,在 app/models/user.rb 裡,你會看到並沒有設定 crypted_password,salt 等欄位,可是生出來的 User object 卻有,就是因為這兩行啦!
class Merb::Authentication

def fetch_user(session_user_id)
Merb::Authentication.user_class.get(session_user_id)
end

def store_user(user)
user.nil? ? user : user.id
end
end
這幾行是告訴 Merb-Auth 怎麼從 session 中取得的東西轉換成登入物件(fetch_user),或是怎麼從登入物件轉換成 session 中所存的東西(store_user)。因為有這個設定的關係,你的登入物件可以和 database 完全沒有關係,甚至只是一個 Boolean 都可以(就是只有登入和沒登入兩種情況,不管登入的人是誰)。預設的 fetch_user 是用 datamapper 去資料庫拿物件出來,如果你用的是 AR 就要改這裡了。

還有一另一個設定檔 merb/merb-auth/strategies.rb
Merb::Authentication.activate!(:default_password_form)
Merb::Authentication.activate!(:default_basic_auth)
這裡是將 default_password_form 和 default_basic_auth 這兩個登入的方式啟動。如果你想用別的認證方式,像是 openid / ldap / AD 之類的,就可以寫在這裡。

接下來我們來解釋一下整個登入的流程。

當我們要去看一個需要登入才能看的頁面時(例子中的 /hello/index 或是 /hello/wazzap ),Merb-Auth 會先看看 session[:user] 是否有東西,有的話表示已經登入了。如果沒有登入的話,它會用各種的方式去登入。這些方式被稱為 Strategies,也就是在 merb/merb-auth/strategies.rb 裡設定的東西。

這些 Strategy 可以取得 Request 的各種參數(cookie/session/url/route/params...),用它們來判斷這個 session 是不是可以成功的登入。當某一個 Strategy 成功時,Merb-Auth 會用設定的 store_user 將 user 存進 session 裡,這樣下一次 request 再來時就可以直接跳過登入的過程了。

當所有的 Strategies 都失敗的時候,Merb-Auth 並不像 Rails 一般的做法,丟一個 302 redirect 到登入的 action 去,而是直接丟 401 Unauthorized 錯誤出來。(你可以用 firebug 去看他的 response status code。別告訴我你沒裝這個 WebDeveloper 最好的朋友。)

那為什麼 401 錯誤會有個 login 的 form 呢?這就是 merb 好玩的地方了!當程式執行中發生的 Exception,都會被 merb 自己 catch 起來,幫他放一個 status code ,然後 re-route 到 app/controllers/exceptions.rb 的某個 action 去。 Merb-Auth 發出來的 Exception 是自己定義的 Unauthenticated,它的 status code 401 ,而對應到的 action 就是 Exceptions#unauthenticated 了。

這個 controller 和一般的 controller 沒什麼兩樣,也可以用 layout,可以用 before/after filter,可以用 template。merb-auth-slice-password 貼心的(或者應該說是雞婆的)幫我們準備了 Exceptions#unauthenticated 這個 aciton 還有它的 view 。所以你如果要改變登入 form 樣子,就自己寫 app/controllers/exceptions.rb 的 unauthenticated 這個 method 和 app/views/exceptions/unauthenticated.html.erb 這個 file 吧。

這一篇我們說明了 Merb-Auth 大致的原理,下一篇就幫我們的 Hello 加上 OpenID 的支援。

沒有留言: