15.3. 驗證
Seam安全中的驗證特性是基於JAAS (Java Authentication and Authorization
Service)開發的,它提供了用來進行使用者身份認證的高度可配置的介面。然而,針對複雜多變的驗證需求,Seam提供了一套非常簡單的驗證方法來隱藏
JAAS的複雜性。[@more@]
15.3. 驗證
Seam安全中的驗證特性是基於JAAS (Java Authentication and Authorization
Service)開發的,它提供了用來進行使用者身份認證的高度可配置的介面。然而,針對複雜多變的驗證需求,Seam提供了一套非常簡單的驗證方法來隱藏
JAAS的複雜性。
15.3.1. 配置一個驗證元件
注意: 如果你使用Seam的身份管理功能(稍後介紹),那麼就不用特地建立一個驗證元件(意味著你可以跳過這一章)。
這種簡單的驗證方法由Seam的一個內建的JAAS登入元件提供,叫做SeamLoginModule,它將驗證功能轉移到你自己編寫的一個Seam元件
之中。該登入模組已經作為Seam的預設程式規則設定好了,你不需要額外的配置檔案。你可以在一個編寫一個你自己的方法來進行驗證,稍經修改也可以用來結
合其他第三方程式進行驗證。這些簡單的配置需要在components.xml中新增一個identity元件:
components>
EL表示式#{authenticator.authenticate}繫結到驗證元件的驗證方法上,該方法被用來對登入的使用者進行驗證。
15.3.2. 編寫驗證方法
components.xml文件中identity的authenticate-
method屬性指出SeamLoginModule將使用哪個方法來進行使用者驗證。該方法沒有引數,並且返回值為boolean型別,用於判斷登入是否
成功。使用者名稱和密碼可以分別從Credentials.getUsername()和Credentials.getPassword()得到。使用者所屬
的角色透過Identity.addRole()來新增。下面就是一個寫在POJO元件中的完整驗證方法:
@Name("authenticator") public class Authenticator { @In EntityManager
entityManager; @In Credentials credentials; @In Identity identity;
public boolean authenticate() { try { User user = (User)
entityManager.createQuery( "from User where username = :username and
password = :password") .setParameter("username",
credentials.getUsername()) .setParameter("password",
credentials.getPassword()) .getSingleResult(); if (user.getRoles() !=
null) { for (UserRole mr : user.getRoles())
identity.addRole(mr.getName()); } return true; } catch
(NoResultException ex) { return false; } } }
在上面的例子中,User和UserRole都是實體。
roles屬性包含了使用者所擁有的角色,這個屬性必須是一個字串組成的Set,例如“admin”、“user”等。上面的程式中,如果符合條件的使用者
記錄沒有找到,那麼丟擲NoResultException 異常,驗證失敗,登入失敗。
提示:
當編寫驗證方法的時候,必須保證這個方法為最簡的,因為Seam安全無法保證這個方法會被呼叫多少次,同一個請求中它可能被呼叫多次。因此,任一判斷成功
或失敗之外的程式碼,應該寫成實現an event observer。想要知道Seam安全所提交事件的更多資訊,請參考後面的安全事件一章。
15.3.2.1. Identity.addRole()
Identity.addRole()方法會根據當前session是否有效來執行不同的任務。如果當前session未經過驗證,那麼
addRole()只會在驗證過程中被呼叫。該方法被呼叫的時候,角色名稱會被加入pre-authenticated的臨時角色列表。一旦驗證成
功,pre-authenticated中的臨時角色會變為真正的角色,並且在呼叫Identity.hasRole()的時候,會返回true。
The following sequence diagram represents the list of pre-authenticated
roles as a first class object to show more clearly how it fits in to
the authentication process.
如果當前session透過驗證,然後呼叫Identity.addRole(),就會立即將指定的角色賦予當前使用者。
15.3.2.2. 編寫一個與安全事件相關的observer
讓我們來討論一個實際例子。當登入成功以後,一些使用者資料需要被更新。這個功能可以透過編寫一個事件觀察器的方式來實現,透過監聽
org.jboss.seam.security.loginSuccessful事件,像這樣:
@In UserStats userStats;
@Observer("org.jboss.seam.security.loginSuccessful") public void
updateUserStats() { userStats.setLastLoginDate(new Date());
userStats.incrementLoginCount(); }
這個觀察器方法可以放在任何地方,甚至是驗證元件裡面。你可以在後面的章節中找到更多關於安全事件的內容。
15.3.3. 編寫一個登入表單
credentials元件裡面提供了username與password屬性,符合絕大多數常用的使用者登入驗證情況。這些屬性可以直接被繫結到登入表單
的相關輸入區上。一旦對這些屬性進行了相關設定,那麼呼叫identity.login()就會使用credentials對使用者進行許可權驗證。下面就是
一個簡單的登入表單例子:
div> div>
div>
和登入類似,退出登入可以呼叫#{identity.logout}。呼叫這個方法會清除當前登入使用者的登入狀態,並且讓當前使用者的session失效。
15.3.4. 配置概述
總的來說,配置登入驗證,只需要三個步驟: * 1、在components.xml中設定登入驗證方法 * 2、編寫一個登入驗證方法 *
3、編寫一個登入表單讓使用者可以有地方登入
15.3.5. 記住我
Seam安全框架提供了一個類似“記住我”的功能,這個功能我們經常能在其他網站上看見。這個功能實際上是提供了兩種不同的習慣或者模式。
第一種模式,瀏覽器將使用者名稱透過cookie的方式儲存下來,只保留密碼文字框讓使用者填寫(市面上常見的瀏覽器都提供了儲存密碼的功能)。
第二種模式,在cookie中保留使用者的唯一性認證資訊,允許使用者下次登入網站的時候不用輸入密碼就能夠自動透過登入許可權驗證。
警告
使用客戶端的cookie記錄使用者資訊,從而實現自動登入的方式是非常危險的。雖然你方便了客戶,但是同時,你站點上的任何一個跨站點指令碼安全漏洞都可能
會造成嚴重的後果。如果沒有登入驗證cookie,那麼駭客唯一能夠透過XSS偷取的資訊就只剩下當前使用者的session了。這就意味著,只有當使用者登
錄網站建立了一個session的一小段時間才可能被攻擊。如果駭客能夠透過“記住我”這個功能從儲存在客戶端的cookie中得到使用者的帳號資訊,那麼
他以後隨時都能夠以客戶的身份透過登入驗證。當然,你需要知道,前面說的這些攻擊能否實現,也取決於你對XSS攻擊做了哪些防範。如果你能夠確保你的網站
100%防禦了XSS攻擊,你完全可以讓使用者輸入的資料全部都顯示在頁面上。只要你能夠實現,這絕對是意見了不起的成就。
現在,幾乎所有瀏覽器廠商都認識到了這個問題,並且提供了一個“儲存密碼”的功能。瀏覽器會為網站的一些頁面,或者是一個domain儲存使用者的使用者名稱和
密碼。這樣使用者在登入網站的時候,即使session未經過登入驗證,瀏覽器也會自動將使用者名稱密碼填入指定的登入表單。網站的設計者可以放一個登入快捷鍵
在頁面上,然後讓瀏覽器去記錄使用者名稱和密碼,這樣實現類似“記住我”的功能會更加安全。一些瀏覽器(例如Safari和OS
X)甚至能夠將使用者的登入資料加密並儲存在作業系統的密碼錶中。如果是在網路環境下,這個密碼錶還可以跟隨使用者到任何地方(例如從使用者的桌上型電腦到筆記
本)。而cookie在通常情況下是無法實現同步的。
總結:你最好在任何情況下都不要使用cookie來實現自動登入驗證。不過,僅僅使用cookie來記住使用者名稱,並且自動將儲存的使用者名稱填寫進入表單中,
就不會有太大的問題,同時也能在一定程度上方便使用者。
如果想要預設開啟“記住我”的功能(僅僅記住使用者名稱),你不需要做任何額外的配置。只需要在登入表單中將記住我的核取方塊繫結到
rememberMe.enabled,就像下面這個例子裡面寫的:
div> div>
div>
15.3.5.1. 基於特徵的記住我驗證
如果你需要使用記住我功能中基於特徵的自動驗證模式,你首先需要配置一個特徵倉庫。最常見的方式就是將驗證特徵儲存在一個資料庫中(Seam支援的資料
庫)。你也可以透過實現org.jboss.seam.security.TokenStore介面的方式實現特徵倉庫。本節假設你使用使用一個虛擬的
JpaTokenStore實現將驗證需要用到的所有特徵資料儲存在資料庫的一個表中。
首先你需要建立一個新的包含了tokens的Entity。下面這個例子展現了一種你可能用到的結構:
@Entity public class AuthenticationToken implements Serializable {
private Integer tokenId; private String username; private String value;
@Id @GeneratedValue public Integer getTokenId() { return tokenId; }
public void setTokenId(Integer tokenId) { this.tokenId = tokenId; }
@TokenUsername public String getUsername() { return username; } public
void setUsername(String username) { this.username = username; }
@TokenValue public String getValue() { return value; } public void
setValue(String value) { this.value = value; } }
在上面的例子中,我們使用了一對特殊的註解,@TokenUsername和@TokenValue,它們用來指出實體的username和特徵屬性。這
些註解標識了實體中需要用來驗證的所有特徵資料。
下一步就是配置JpaTokenStore,使用entity
bean來儲存或讀取驗證特徵。具體的操作為,在components.xml中加入一個token-class屬性:
然後,在components.xml加入RememberMe設定,並且將模式設定為autoLogin:
透過上面這些設定,使用者每次訪問你的網站的時候就可以自動登入了(只要使用者點選了“記住我”核取方塊)。
15.3.6. 處理安全異常
在登入的時候難免出現異常,為了防止使用者看見預設的錯誤頁面,我們建議你在pages.xml裡面配置一下頁面自動跳轉設定,這樣就能夠在出錯的時候自動跳轉到一些比較“漂亮”的頁面上。最常見的兩種安全異常為:
*
NotLoggedInException:這個異常在使用者沒有登入就嘗試訪問需要登入才能訪問的頁面的時候出現。
*
AuthorizationException:這個異常只出現在這種情況,使用者已經登入了,並且訪問某個頁面,但是他並沒有訪問這個頁面的許可權。
在出現NotLoggedInException異常的情況下,我們建議將頁面跳轉到登陸頁面或者註冊頁面
在出現AuthorizationException異常的時候,可以跳轉到一個提示使用者沒有相應許可權的頁面。
下面是一個pages.xml檔案的片段,這裡面配置了這兩種安全異常出現時的跳轉規則:
... You must be logged in to perform this actionmessage> redirect> exception> You do not have the necessary security privileges to perform this action.message> redirect> exception> pages>
大部分web應用程式會需要更多的跳轉規則,用以處理各種實際業務邏輯。所以Seam包含了一些特殊的功能用來處理這些問題。
15.3.7. 登入重定向
當使用者在沒有登入的情況下試圖訪問某個需要登入才可以訪問的頁面(或者是一組用萬用字元指定的頁面)的時候,Seam會將使用者轉到一個登入頁面:
... pages>
當使用者登入了以後,我們需要自動跳轉到使用者剛才未登入時嘗試訪問的頁面。透過在components.xml中進行一下配置,就可以實現。並且跳轉回剛才的頁面的時候,所有的請求引數都會被保留。
event> event>
需要注意一下,登入跳轉功能是基於會話範圍實現的,所以不要在authenticate()方法中結束當前會話。
15.3.8. HTTP驗證
Seam也提供了HTTP Basic或HTTP Digest (RFC 2617)方式的驗證。不過除非必要,我們不建議使用這些方式。在components.xml檔案中,我們需要進行以下配置:
如果需要使用digest驗證方式,key和realm也需要設定一下:
key
屬性可以是任意值。realm為使用者在登入的時候需要告訴使用者的realm名稱。
15.3.8.1. 編寫Digest驗證器
當使用digest驗證方式的時候,你的驗證類需要整合自抽象類
org.jboss.seam.security.digest.DigestAuthenticator,並且使用
validatePassword()方法來驗證使用者的純文字密碼。例如: public boolean authenticate() { try
{ User user = (User) entityManager.createQuery( "from User where
username = :username") .setParameter("username",
identity.getUsername()) .getSingleResult(); return
validatePassword(user.getPassword()); } catch (NoResultException ex) {
return false; } }
15.3.9. 高階驗證特性
本節將探索一些安全API中提供的高階驗證特性,用來滿足各種複雜的驗證需求。
15.3.9.1. 使用容器的JAAS配置
如果你不需要使用Seam Security
API提供的簡化了的JAAS配置,那麼你可以將其委託給系統預設的JAAS配置。你可以透過在components.xml配置一個jaas-
config-name來實現。例如,如果你使用JBoss AS,並且希望使用其他的驗證策略(例如使用JBoss AS提供的
UsersRolesLoginModule 登入模組)。你需要這麼設定:
需要記住的是,透過上面這些配置,並不是讓使用者驗證交給了部署Seam應用程式的容器。它僅僅是告訴Seam安全框架使用在容器中配置的JAAS安全規則來驗證使用者。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/249132/viewspace-1023214/,如需轉載,請註明出處,否則將追究法律責任。