Jive安全管理機(jī)制
在Jive中除了前面介紹的有關(guān)設(shè)計(jì)模式實(shí)現(xiàn)組件外,還有其他有一定特點(diǎn)的組件功能,分析研究這些組件功能可以更加完整透徹地理解Jive論壇系統(tǒng)。

Jive安全管理機(jī)制基本是由下列部分組成:

·    安全驗(yàn)證機(jī)制。主要是驗(yàn)證用戶名和密碼組合是否與數(shù)據(jù)庫(kù)中注冊(cè)時(shí)的數(shù)據(jù)一致,以確認(rèn)該用戶身份為注冊(cè)用戶。
這是對(duì)所有的JSP訪問都進(jìn)行攔截訪問。

·    訪問權(quán)限控制(ACL)。對(duì)不同的數(shù)據(jù)不同用戶擁有不同的訪問權(quán)限,例如,一個(gè)帖子普通用戶可以瀏覽,但是不能
更該;但是管理員卻可以編輯刪除。這部分功能是通過代理模式實(shí)現(xiàn),為每個(gè)關(guān)鍵數(shù)據(jù)都建立一個(gè)代理類用來(lái)實(shí)現(xiàn)訪問
權(quán)限檢查,這在前面討論過。

·    用戶資料管理系統(tǒng)。主要是管理用戶的資料數(shù)據(jù),進(jìn)行用戶組和用戶關(guān)系的建立等。

 安全驗(yàn)證機(jī)制
Jive的安全驗(yàn)證機(jī)制是按照比較通用的思路設(shè)計(jì)的。類似前面“簡(jiǎn)單的用戶注冊(cè)管理系統(tǒng)”中的介紹,Jive也是在所有
的JSP頁(yè)面中include一個(gè)安全檢驗(yàn)功能的global.jsp。由于global.jsp是在每個(gè)JSP一開始必須執(zhí)行的功能,因此通過
攔截global.jsp攔截發(fā)往各個(gè)JSP頁(yè)面的請(qǐng)求(request)。如果這個(gè)請(qǐng)求是合法的,將被允許通過;如果不是,
將注明請(qǐng)求者身份是Anonymous(匿名者)。

global.jsp代碼如下:

boolean isGuest = false;
Authorization authToken 
= SkinUtils.getUserAuthorization(request, response);
if (authToken == null{//未被驗(yàn)證通過
    authToken = AuthorizationFactory.getAnonymousAuthorization();
    isGuest
=true;
}

在Jive中,以Authorization對(duì)象作為驗(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)該可以找到用戶名和密碼,這樣就省卻了用戶再次從鍵盤輸入用戶名和密碼,將用戶名和密碼
通過下列語(yǔ)句進(jìn)行數(shù)據(jù)庫(kù)驗(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),是使用工廠模式加動(dòng)態(tài)類反射機(jī)制完成的,代碼如下:

public abstract class AuthorizationFactory {
   
//定義一個(gè)數(shù)據(jù)庫(kù)具體實(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();

    
//動(dòng)態(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)證。如果無(wú)法通
過驗(yàn)證,有兩個(gè)信息實(shí)現(xiàn)顯示:一個(gè)是拋出UnauthorizedException,另外一個(gè)是返回空的Authorization對(duì)象。

那么,子類DbAuthorizationFactory毫無(wú)疑問就是查詢數(shù)據(jù)庫(kù),將輸入的用戶名和密碼與數(shù)據(jù)庫(kù)保存的用戶名和密碼進(jìn)行校驗(yàn)。

Jive的安全驗(yàn)證機(jī)制比較簡(jiǎn)單易懂,值得在實(shí)踐中學(xué)習(xí)借鑒。但是注意到這套安全驗(yàn)證機(jī)制只是Web層的“手工”驗(yàn)證,資源
訪問權(quán)限(ACL)也是自己“手工”來(lái)實(shí)現(xiàn)的。如果使用EJB技術(shù),因?yàn)镋JB容器本身有一定的資源訪問控制體系,因此在Web
層驗(yàn)證通過后,需要將這些登錄信息傳遞到EJB層。當(dāng)然如果直接使用Web容器的安全驗(yàn)證機(jī)制,那么Web層與EJB層之間的
登錄信息傳遞將由容器實(shí)現(xiàn),這樣就更加簡(jiǎn)單方便。

Jive這種的安全驗(yàn)證并不是使用Web容器的安全驗(yàn)證機(jī)制,如何使用Web容器的安全驗(yàn)證機(jī)制將在以后章節(jié)介紹。盡管如此
,Jive這套安全驗(yàn)證機(jī)制對(duì)付小型系統(tǒng)的應(yīng)用也是足夠的。


用戶資料管理
在Jive中,用戶User對(duì)象的操作訪問類似于論壇Forum對(duì)象的訪問,與User對(duì)象有關(guān)的操作都封裝在一個(gè)類中操作,這是
外觀(Facade)模式的應(yīng)用。

在Jive中,用戶資料管理屬于大系統(tǒng)中的一個(gè)子系統(tǒng),在這個(gè)子系統(tǒng)中,用戶子系統(tǒng)和其他系統(tǒng)又有一定的關(guān)系,涉及
的類不少,通過建立一個(gè)UserManager類來(lái)統(tǒng)一對(duì)外接口,使得整個(gè)子系統(tǒng)條目結(jié)構(gòu)清晰。

UserManager中無(wú)外乎用戶數(shù)據(jù)的管理,如用戶的創(chuàng)建、修改、查詢和刪除。DbUserManager是UserManager的一個(gè)數(shù)據(jù)庫(kù)
實(shí)現(xiàn),可是看看DbUserManager中除了刪除功能是直接通過SQL語(yǔ)句進(jìn)行數(shù)據(jù)庫(kù)刪除操作外,其他都委托給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 {
            
//以u(píng)sername查詢改用戶是否存在
            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ù)庫(kù)插入數(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ù)庫(kù)存儲(chǔ),這也類似EJB中
CMP實(shí)體Bean的數(shù)據(jù)字段修改保存。

Jive中組Group與用戶User處理幾乎差不多,只是在Group中整合了權(quán)限方面的信息,這種做法是有一定的局限性,不是很
值得借鑒,要想設(shè)計(jì)一個(gè)動(dòng)態(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)限,
那么用戶組也接近角色的概念。