Jive安全管理机制
在Jive中除了前面介绍的有关设计模式实现组件外,还有其他有一定特点的组件功能,分析研究这些组件功能可以更加完整透彻地理解Jive论坛系统。

Jive安全管理机制基本是由下列部分组成:

·    安全验证机制。主要是验证用户名和密码组合是否与数据库中注册时的数据一致,以确认该用户身份为注册用户。
这是对所有的JSP访问都进行拦截访问。

·    访问权限控制(ACL)。对不同的数据不同用户拥有不同的访问权限,例如,一个帖子普通用户可以浏览,但是不能
更该;但是管理员却可以编辑删除。这部分功能是通过代理模式实现,为每个关键数据都建立一个代理类用来实现访问
权限检查,这在前面讨论过。

·    用户资料管理系统。主要是管理用户的资料数据,进行用户组和用户关系的建立等。

 安全验证机制
Jive的安全验证机制是按照比较通用的思路设计的。类似前面“简单的用户注册管理系统”中的介绍,Jive也是在所有
的JSP页面中include一个安全检验功能的global.jsp。由于global.jsp是在每个JSP一开始必须执行的功能,因此通过
拦截global.jsp拦截发往各个JSP页面的请求(request)。如果这个请求是合法的,将被允许通过;如果不是,
将注明请求者身份是Anonymous(匿名者)。

global.jsp代码如下:

boolean isGuest = false;
Authorization authToken 
= SkinUtils.getUserAuthorization(request, response);
if (authToken == null{//未被验证通过
    authToken = AuthorizationFactory.getAnonymousAuthorization();
    isGuest
=true;
}

在Jive中,以Authorization对象作为验证通过的标志,它的接口代码如下:

public interface Authorization {
    
public long getUserID();    
    
public boolean isAnonymous();
}


具体实现是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;
    }

}

此类只是一个userID,因此只是一个象征性的标志。
SkinUtils是一个为JSP服务的类,它的getUserAuthorization代码如下:
public static Authorization getUserAuthorization
        (HttpServletRequest request, HttpServletResponse response)
  
{
   HttpSession session 
= request.getSession();
    
// 从HttpSession中获取Authorization实例
    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中获得用户名和密码后,进行安全验证
           authToken = AuthorizationFactory.getAuthorization(username,password);
        }
catch (Exception e) {}
        
// put that token in the user's session:
        if (authToken != null{//如果通过验证,保存authToken在http Session
           session.setAttribute(JIVE_AUTH_TOKEN, authToken);
        }

       
// return the authorization token
        return authToken;
    }

    
return null;
}


用户验证预先通过两个步骤。首先检查HttpSession中是否保存了该用户的验证信息,如果用户第一次验证通过,反复
访问,这道关口检查就可以通过。
如果HttpSession中没有验证信息,那么从该用户的浏览器cookie中寻找用户名和密码。如果该用户激活了cookie保存
这些登录信息,那么应该可以找到用户名和密码,这样就省却了用户再次从键盘输入用户名和密码,将用户名和密码
通过下列语句进行数据库验证:
authToken = AuthorizationFactory.getAuthorization(username,password);

这一举是验证关键。AuthorizationFactory是一个抽象类,定义了Jive安全验证机制所需的所有方法,AuthorizationFactory的实现类似前面讨论
的ForumFactory实现,是使用工厂模式加动态类反射机制完成的,代码如下:

public abstract class AuthorizationFactory {
   
//定义一个数据库具体实现
    private static String className =
        
" com.Yasna.forum.database.DbAuthorizationFactory";
    
private static AuthorizationFactory factory = null;
    
//验证方法 如果没有UnauthorizedException抛出,表示验证通过
    public static Authorization getAuthorization(String username,
            String password) 
throws UnauthorizedException
    
{
        loadAuthorizationFactory();
        
return factory.createAuthorization(username, password);
    }


    
//匿名者处理方法
    public static Authorization getAnonymousAuthorization() {
        loadAuthorizationFactory();
        
return factory.createAnonymousAuthorization();
    }

    
//需要具体实现的抽象方法
    protected abstract Authorization createAuthorization(String username,
            String password) 
throws UnauthorizedException;
    
protected abstract Authorization createAnonymousAuthorization();

    
//动态配置AuthorizationFactory的具体实现,可以在配置文件中定义一个
    
//基于LDAP的实现。类似ForumFactory的getInstance方法
    private static void loadAuthorizationFactory() {
        …
    }

}

AuthorizationFactory看上去很复杂,实际只有一个核心方法getAuthorization。实现用户名和密码的验证。如果无法通
过验证,有两个信息实现显示:一个是抛出UnauthorizedException,另外一个是返回空的Authorization对象。

那么,子类DbAuthorizationFactory毫无疑问就是查询数据库,将输入的用户名和密码与数据库保存的用户名和密码进行校验。

Jive的安全验证机制比较简单易懂,值得在实践中学习借鉴。但是注意到这套安全验证机制只是Web层的“手工”验证,资源
访问权限(ACL)也是自己“手工”来实现的。如果使用EJB技术,因为EJB容器本身有一定的资源访问控制体系,因此在Web
层验证通过后,需要将这些登录信息传递到EJB层。当然如果直接使用Web容器的安全验证机制,那么Web层与EJB层之间的
登录信息传递将由容器实现,这样就更加简单方便。

Jive这种的安全验证并不是使用Web容器的安全验证机制,如何使用Web容器的安全验证机制将在以后章节介绍。尽管如此
,Jive这套安全验证机制对付小型系统的应用也是足够的。


用户资料管理
在Jive中,用户User对象的操作访问类似于论坛Forum对象的访问,与User对象有关的操作都封装在一个类中操作,这是
外观(Facade)模式的应用。

在Jive中,用户资料管理属于大系统中的一个子系统,在这个子系统中,用户子系统和其他系统又有一定的关系,涉及
的类不少,通过建立一个UserManager类来统一对外接口,使得整个子系统条目结构清晰。

UserManager中无外乎用户数据的管理,如用户的创建、修改、查询和删除。DbUserManager是UserManager的一个数据库
实现,可是看看DbUserManager中除了删除功能是直接通过SQL语句进行数据库删除操作外,其他都委托给User的具体实
现DbUser实现的。这种实现非常类似于EJB中Session Bean和实体Bean之间的关系。以创建用户资料为例,代码如下:

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) {
            
//该用户不存在,创建一个新用户
            newUser = new DbUser(username, password, email, factory);
        }

        
return newUser;
}


DbUser的构造方法实际是用户资料的新增创建:

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();              
//数据库插入数据
}



在Jive中,数据修改的保存是由DbUser的saveToDb方法实现的,而saveToDb方法调用是在每个setXXXX方法中。即每当外界
调用DbUser的setXXXX,则表示需要改变某些字段属性值,在这个方法中直接进行数据库存储,这也类似EJB中
CMP实体Bean的数据字段修改保存。

Jive中组Group与用户User处理几乎差不多,只是在Group中整合了权限方面的信息,这种做法是有一定的局限性,不是很
值得借鉴,要想设计一个动态扩展灵活的权限系统,必须在用户或组与权限之间引入角色概念,也就是比较先进的基于
角色的权限系统(RBAC Roled-Based Access Control,相关网址:http://csrc.nist.gov/rbac/)。

在RBAC中,用户组只是用户的一个集合,应该是通过角色和权限发生联系。所以RBAC认为,如果给用户组赋予权限,
那么用户组也接近角色的概念。