3 Jive安全管理机制
在Jive中除了前面介绍的有关设计模式实现组件外,还有其他有一定特点的组件功能,分析研究这些组件功能可以更加完整透彻地理解Jive论坛系统。
Jive安全管理机制基本是由下列部分组成:
· 安全验证机制。主要是验证用户名和密码组合是否与数据库中注册时的数据一致,以确认该用户身份为注册用户。这是对所有的JSP访问都进行拦截访问。
· 访问权限控制(ACL)。对不同的数据不同用户拥有不同的访问权限,例如,一个帖子普通用户可以浏览,但是不能更改;但是管理员却可以编辑删除。这部分功能是通过代理模式实现,为每个关键数据都建立一个代理类用来实现访问权限检查,这在前面讨论过。
· 用户资料管理系统。主要是管理用户的资料数据,进行用户组和用户关系的建立等。
1 安全验证机制
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这套安全验证机制对付小型系统的应用也是足够的。
2 用户资料管理
在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认为,如果给用户组赋予权限,那么用户组也接近角色的概念。