3? Jive 安全管理機(jī)制
在 Jive 中除了前面介紹的有關(guān)設(shè)計(jì)模式實(shí)現(xiàn)組件外,還有其他有一定特點(diǎn)的組件功能,分析研究這些組件功能可以更加完整透徹地理解 Jive 論壇系統(tǒng)。
Jive 安全管理機(jī)制基本是由下列部分組成:
·????????? 安全驗(yàn)證機(jī)制。主要是驗(yàn)證用戶名和密碼組合是否與數(shù)據(jù)庫中注冊時(shí)的數(shù)據(jù)一致,以確認(rèn)該用戶身份為注冊用戶。這是對所有的 JSP 訪問都進(jìn)行攔截訪問。
·????????? 訪問權(quán)限控制( ACL )。對不同的數(shù)據(jù)不同用戶擁有不同的訪問權(quán)限,例如,一個(gè)帖子普通用戶可以瀏覽,但是不能更該;但是管理員卻可以編輯刪除。這部分功能是通過代理模式實(shí)現(xiàn),為每個(gè)關(guān)鍵數(shù)據(jù)都建立一個(gè)代理類用來實(shí)現(xiàn)訪問權(quán)限檢查,這在前面討論過。
·????????? 用戶資料管理系統(tǒng)。主要是管理用戶的資料數(shù)據(jù),進(jìn)行用戶組和用戶關(guān)系的建立等。
1? 安全驗(yàn)證機(jī)制
Jive 的安全驗(yàn)證機(jī)制是按照比較通用的思路設(shè)計(jì)的。類似前面“簡單的用戶注冊管理系統(tǒng)”中的介紹, Jive 也是在所有的 JSP 頁面中 include 一個(gè)安全檢驗(yàn)功能的 global.jsp 。由于 global.jsp 是在每個(gè) JSP 一開始必須執(zhí)行的功能,因此通過攔截 global.jsp 攔截發(fā)往各個(gè) JSP 頁面的請求( request )。如果這個(gè)請求是合法的,將被允許通過;如果不是,將注明請求者身份是 Anonymous (匿名者)。
global.jsp 代碼如下:
boolean isGuest = false;
Authorization authToken = SkinUtils.getUserAuthorization(request, response);
if (authToken == null) {// 未被驗(yàn)證通過
??? authToken = AuthorizationFactory.getAnonymousAuthorization();
??? isGuest=true;
}
在 Jive 中,以 Authorization 對象作為驗(yàn)證通過的標(biāo)志,它的接口代碼如下:
public interface Authorization {
??? public long getUserID();???
??? public boolean isAnonymous();
}
具體實(shí)現(xiàn)是 DbAuthorization ,代碼如下:
public final class DbAuthorization implements Authorization, Serializable {
??? private long userID;
??? protected DbAuthorization(long userID) {
??????? this.userID = userID;
??? }
??? public long getUserID() {
??????? return userID;
??? }
??? public boolean isAnonymous() {
??????? return userID == -1;
??? }
}
此類只是一個(gè) userID ,因此只是一個(gè)象征性的標(biāo)志。
SkinUtils 是一個(gè)為 JSP 服務(wù)的類,它的 getUserAuthorization 代碼如下:
public static Authorization getUserAuthorization
??????? (HttpServletRequest request, HttpServletResponse response)
? {
??? HttpSession session = request.getSession();
??? // 從 HttpSession 中獲取 Authorization 實(shí)例
??? Authorization authToken =
(Authorization)session.getAttribute(JIVE_AUTH_TOKEN);
??? if (authToken != null) {???? return authToken;? }
?
??? // 如果 HttpSession 中沒有,檢查用戶瀏覽器 cookie
??? Cookie cookie = getCookie(request, JIVE_AUTOLOGIN_COOKIE);
??? if (cookie != null) {
??????? try {
?????????? String[] values = decodePasswordCookie(cookie.getValue());
?????????? String username = values[0];
?????????? String password = values[1];
?????????? // 從 cookie 中獲得用戶名和密碼后,進(jìn)行安全驗(yàn)證
?????????? authToken = AuthorizationFactory.getAuthorization(username,password);
??????? }catch (Exception e) {}
??????? // put that token in the user's session:
??????? if (authToken != null) {// 如果通過驗(yàn)證,保存 authToken 在 http Session
?????????? session.setAttribute(JIVE_AUTH_TOKEN, authToken);
??????? }
?????? // return the authorization token
??????? return authToken;
??? }
? ??return null;
}
用戶驗(yàn)證預(yù)先通過兩個(gè)步驟。首先檢查 HttpSession 中是否保存了該用戶的驗(yàn)證信息,如果用戶第一次驗(yàn)證通過,反復(fù)訪問,這道關(guān)口檢查就可以通過。
如果 HttpSession 中沒有驗(yàn)證信息,那么從該用戶的瀏覽器 cookie 中尋找用戶名和密碼。如果該用戶激活了 cookie 保存這些登錄信息,那么應(yīng)該可以找到用戶名和密碼,這樣就省卻了用戶再次從鍵盤輸入用戶名和密碼,將用戶名和密碼通過下列語句進(jìn)行數(shù)據(jù)庫驗(yàn)證:
authToken = AuthorizationFactory.getAuthorization(username,password);
這一舉是驗(yàn)證關(guān)鍵。 AuthorizationFactory 是一個(gè)抽象類,定義了 Jive 安全驗(yàn)證機(jī)制所需的所有方法, AuthorizationFactory 的實(shí)現(xiàn)類似前面討論的 ForumFactory 實(shí)現(xiàn),是使用工廠模式加動態(tài)類反射機(jī)制完成的,代碼如下:
public abstract class AuthorizationFactory {
?? // 定義一個(gè)數(shù)據(jù)庫具體實(shí)現(xiàn)
??? private static String className =
??????? " com.Yasna.forum.database.DbAuthorizationFactory";
?
??? private static AuthorizationFactory factory = null;
??? // 驗(yàn)證方法 如果沒有 UnauthorizedException 拋出,表示驗(yàn)證通過
??? public static Authorization getAuthorization(String username,
??????????? String password) throws UnauthorizedException
??? {
??????? loadAuthorizationFactory();
??????? return factory.createAuthorization(username, password);
??? }
??? // 匿名者處理方法
??? public static Authorization getAnonymousAuthorization() {
??????? loadAuthorizationFactory();
??????? return factory.createAnonymousAuthorization();
??? }
??? // 需要具體實(shí)現(xiàn)的抽象方法
??? protected abstract Authorization createAuthorization(String username,
??????????? String password) throws UnauthorizedException;
??? protected abstract Authorization createAnonymousAuthorization();
??? // 動態(tài)配置 AuthorizationFactory 的具體實(shí)現(xiàn),可以在配置文件中定義一個(gè)
??? // 基于 LDAP 的實(shí)現(xiàn)。類似 ForumFactory 的 getInstance 方法
??? private static void loadAuthorizationFactory() {
??????? …
??? }
}
AuthorizationFactory 看上去很復(fù)雜,實(shí)際只有一個(gè)核心方法 getAuthorization 。實(shí)現(xiàn)用戶名和密碼的驗(yàn)證。如果無法通過驗(yàn)證,有兩個(gè)信息實(shí)現(xiàn)顯示:一個(gè)是拋出 UnauthorizedException ,另外一個(gè)是返回空的 Authorization 對象。
那么,子類 DbAuthorizationFactory 毫無疑問就是查詢數(shù)據(jù)庫,將輸入的用戶名和密碼與數(shù)據(jù)庫保存的用戶名和密碼進(jìn)行校驗(yàn)。
Jive 的安全驗(yàn)證機(jī)制比較簡單易懂,值得在實(shí)踐中學(xué)習(xí)借鑒。但是注意到這套安全驗(yàn)證機(jī)制只是 Web 層的“手工”驗(yàn)證,資源訪問權(quán)限( ACL )也是自己“手工”來實(shí)現(xiàn)的。如果使用 EJB 技術(shù),因?yàn)?/span> EJB 容器本身有一定的資源訪問控制體系,因此在 Web 層驗(yàn)證通過后,需要將這些登錄信息傳遞到 EJB 層。當(dāng)然如果直接使用 Web 容器的安全驗(yàn)證機(jī)制,那么 Web 層與 EJB 層之間的登錄信息傳遞將由容器實(shí)現(xiàn),這樣就更加簡單方便。
Jive 這種的安全驗(yàn)證并不是使用 Web 容器的安全驗(yàn)證機(jī)制,如何使用 Web 容器的安全驗(yàn)證機(jī)制將在以后章節(jié)介紹。盡管如此, Jive 這套安全驗(yàn)證機(jī)制對付小型系統(tǒng)的應(yīng)用也是足夠的。
2? 用戶資料管理
在 Jive 中,用戶 User 對象的操作訪問類似于論壇 Forum 對象的訪問,與 User 對象有關(guān)的操作都封裝在一個(gè)類中操作,這是外觀( Facade )模式的應(yīng)用。
在 Jive 中,用戶資料管理屬于大系統(tǒng)中的一個(gè)子系統(tǒng),在這個(gè)子系統(tǒng)中,用戶子系統(tǒng)和其他系統(tǒng)又有一定的關(guān)系,涉及的類不少,通過建立一個(gè) UserManager 類來統(tǒng)一對外接口,使得整個(gè)子系統(tǒng)條目結(jié)構(gòu)清晰。
UserManager 中無外乎用戶數(shù)據(jù)的管理,如用戶的創(chuàng)建、修改、查詢和刪除。 DbUserManager 是 UserManager 的一個(gè)數(shù)據(jù)庫實(shí)現(xiàn),可是看看 DbUserManager 中除了刪除功能是直接通過 SQL 語句進(jìn)行數(shù)據(jù)庫刪除操作外,其他都委托給 User 的具體實(shí)現(xiàn) DbUser 實(shí)現(xiàn)的。這種實(shí)現(xiàn)非常類似于 EJB 中 Session Bean 和實(shí)體 Bean 之間的關(guān)系。以創(chuàng)建用戶資料為例,代碼如下:
public User createUser(String username, String password, String email)
??????????? throws UserAlreadyExistsException
?{
??????? User newUser = null;
??????? try {
??????????? // 以 username 查詢改用戶是否存在
??????????? User existingUser = getUser(username);
??????????? // 如果沒有拋出 UserNotFoundException 異常,表示該用戶存在
??????????? //The user already exists since now exception, so:
??????????? throw new UserAlreadyExistsException();
??????? } catch (UserNotFoundException unfe) {
??????????? // 該用戶不存在,創(chuàng)建一個(gè)新用戶
??????????? newUser = new DbUser(username, password, email, factory);
??????? }
??????? return newUser;
}
DbUser 的構(gòu)造方法實(shí)際是用戶資料的新增創(chuàng)建:
protected DbUser(String username, String password, String email,
??????????? DbForumFactory factory)
{
??????? this.id = SequenceManager.nextID(JiveGlobals.USER);? // 獲得自增 ID
??????? this.username = username;
??????? // Compute hash of password.
??? ????this.passwordHash = StringUtils.hash(password);? // 獲得加密的密碼
??????? this.email = email;
??????? this.factory = factory;
??????? long now = System.currentTimeMillis();
??????? creationDate = new java.util.Date(now);
??????? modifiedDate = new java.util.Date(now);
??????? properties = new Hashtable();
??????? insertIntoDb();????????????? // 數(shù)據(jù)庫插入數(shù)據(jù)
}
在 Jive 中,數(shù)據(jù)修改的保存是由 DbUser 的 saveToDb 方法實(shí)現(xiàn)的,而 saveToDb 方法調(diào)用是在每個(gè) setXXXX 方法中。即每當(dāng)外界調(diào)用 DbUser 的 setXXXX ,則表示需要改變某些字段屬性值,在這個(gè)方法中直接進(jìn)行數(shù)據(jù)庫存儲,這也類似 EJB 中 CMP 實(shí)體 Bean 的數(shù)據(jù)字段修改保存。
Jive 中組 Group 與用戶 User 處理幾乎差不多,只是在 Group 中整合了權(quán)限方面的信息,這種做法是有一定的局限性,不是很值得借鑒,要想設(shè)計(jì)一個(gè)動態(tài)擴(kuò)展靈活的權(quán)限系統(tǒng),必須在用戶或組與權(quán)限之間引入角色概念,也就是比較先進(jìn)的基于角色的權(quán)限系統(tǒng)( RBAC Roled-Based Access Control ,相關(guān)網(wǎng)址: http://csrc.nist.gov/rbac/ )。
在 RBAC 中,用戶組只是用戶的一個(gè)集合,應(yīng)該是通過角色和權(quán)限發(fā)生聯(lián)系。所以 RBAC 認(rèn)為,如果給用戶組賦予權(quán)限,那么用戶組也接近角色的概念。