您的位置:首页 > 其它

基于容器的用户安全管理系统和JMS(3)

2008-05-01 05:46 627 查看
3 详细设计和实现

图6-5 业务模型图

本项目功能需求中用户资料管理功能将需要详细的类设计和实现,而权限的验证和授权以及可控制的资源限制访问两个功能是由J2EE容器实现的,需要进行详细的配置和设置。下面就分这两个方向将本项目逐步具体实现。
3.1 业务对象建模
首先确定本项目的Domain Model,或者可以称为基本业务对象,如图6-5所示。
Role代表角色,User代表用户,group代表用户组,用户组是用户的集合,多个用户可以对应同一个角色。角色定义需要是灵活的,可以增减修改的。角色Role接口如下:
public interface Role extends java.io.Serializable{
public String getRoleId();
public void setRoleId(String roleId);
//获得角色名称
public String getName();
public void setName(String name);
}
用户User的接口定义:
public interface User extends java.io.Serializable{
//用户的名称
public String getName();
public void setName(String name) ;
//用户Id
public String getUserId() ;
public void setUserId(String userId) ;
//用户密码
public String getPassword();
public void setPassword(String password);
//用户E-mail
public String getEmail();
public void setEmail(String email);
}
在用户接口定义中,只定义关键常用字段的操作行为,有关用户的地址、电话等其他信息使用其他对象表示,可以根据具体应用系统不同的要求再设计。
用户组group的接口定义如下:
public interface Group extends java.io.Serializable{
public String getGroupId();
public void setGroupId(String groupId);
//用户组名称
public String getName();
public void setName(String name);
}
用户组是用来代表一组用户,可以指定一个用户组为一个特定角色,那么该用户组下的所有用户也将拥有该角色的访问权限能力。
用户和角色的直接关系设定为多对一,每个用户只能为一个角色,这样可以使问题变得简单些。
3.2 数据库设计
依据业务对象模型建立相应的数据模型,同时使用专门关联表来实现业务对象之间的关系。在EJB 2.0以上容器中,可以使用实体Bean的CMR来表示一对多、多对一或多对多的关系。这样就无需写很多代码,但是使用数据表来表示关系更容易把握,更方便理解和使用。
以MySQL数据库为例,下面是用户User的数据表结构:
CREATE TABLE user (
userId varchar(50) binary NOT NULL default ', #用户Id
password varchar(50) binary default NULL, #密码
name varchar(50) default NULL, #用户名
email varchar(50) default NULL, #E-mail邮件地址
PRIMARY KEY (userId),
UNIQUE KEY email (email),
UNIQUE KEY name (name)
) TYPE=InnoDB; #使用MySQL的InnoDB
以下是角色Role的数据表结构:
CREATE TABLE role (
roleId varchar(50) binary NOT NULL default ', #角色Id
name varchar(100) default NULL, #角色名称
PRIMARY KEY (roleId)
) TYPE=InnoDB;
使用下表保存用户和角色之间关系:
CREATE TABLE users_roles (
userId varchar(50) NOT NULL default ', #用户Id
roleId varchar(50) NOT NULL default ' #角色Id
) TYPE=InnoDB;
用户组group、用户组与用户关系以及用户组与角色关系与此类似。
3.3 实体bean实现
在EJB层将实现用户资料管理的主要功能,可以使用EJB CMP实现各个数据模型,其中User的实体bean如下。
Bean实现:
import javax.ejb.*;
abstract public class UserBean implements EntityBean {
EntityContext entityContext;
public java.lang.String ejbCreate(java.lang.String userId) throws CreateException {
setUserId(userId);
return null;
}
public void ejbPostCreate(java.lang.String userId) throws CreateException {
}
public void ejbRemove() throws RemoveException {
}
public abstract void setUserId(java.lang.String userId);
public abstract void setPassword(java.lang.String password);
public abstract void setName(java.lang.String name);
public abstract void setEmail(java.lang.String email);
public abstract java.lang.String getUserId();
public abstract java.lang.String getPassword();
public abstract java.lang.String getName();
public abstract java.lang.String getEmail();
public void ejbLoad() {
}
public void ejbStore() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void unsetEntityContext() {
this.entityContext = null;
}
public void setEntityContext(EntityContext entityContext) {
this.entityContext = entityContext;
}
}
Local Home接口为:
import javax.ejb.*;
import java.util.*;
public interface UserHome extends javax.ejb.EJBLocalHome {
public UserLocal create(String userId) throws CreateException;
public UserLocal findByEmail(String email) throws FinderException;
public UserLocal findByName(String name) throws FinderException;
public UserLocal findByPrimaryKey(String userId) throws FinderException;
}
Local接口为:
import javax.ejb.*;
import java.util.*;
public interface UserLocal extends javax.ejb.EJBLocalObject {
public String getUserId();
public void setPassword(String password);
public String getPassword();
public void setName(String name);
public String getName();
public void setEmail(String email);
public String getEmail();
}
相应的ejb-jar.xml为:

User
User
com.jdon.security.auth.ejb.UserHome
com.jdon.security.auth.ejb.UserLocal
com.jdon.security.auth.ejb.UserBean
Container
java.lang.String
False
2.x
User

userId

password

name

email

userId

findByEmail

java.lang.String

SELECT OBJECT(s) FROM User AS s WHERE s.email=?1

findByName

java.lang.String

SELECT OBJECT(s) FROM User AS s WHERE s.name=?1

在该实体bean中,使用EJB-QL实现了以E-mail或用户名为关键字的查询语句。
其他数据表都可以采取类似上述方法建立,当然使用Jbuilder专门的EJB可视化开发工具,可以直接从数据库中将这些表自动导入成相应的实体bean,降低开发量。
3.4 Session Bean实现
本项目需要一个Facade类统一实现用户资料的操作,建立无状态Session Bean 为UserManager的EJB。在这个类中,主要实现有关用户的新增、删除或修改。代码如下:
public class UserManagerBean implements SessionBean {
private final static Logger logger = Logger.getLogger(UserManagerBean.class);
SessionContext sessionContext;
UserHome userHome;
UsersRolesHome usersRolesHome;
UsersGroupsHome usersGroupsHome;
RoleManagerLocalHome roleManagerLocalHome;
SequenceGeneratorLocalHome sequenceHome;
// ejbCreate()一般只执行一次,以后再调用时不再执行,通过ejbCreate()可以
//实现一些类属性的缓冲
public void ejbCreate() throws CreateException {
try {
ServiceLocator serviceLocator = new ServiceLocator();
userHome = (UserHome) serviceLocator.getLocalHome(
JNDINames.USER_HOME);
sequenceHome = (SequenceGeneratorLocalHome)
serviceLocator.getLocalHome(JNDINames.SEQUENCEGENERATOR_HOME);
usersRolesHome = (UsersRolesHome) serviceLocator.getLocalHome(
JNDINames.USERSROLES_HOME);
usersGroupsHome = (UsersGroupsHome) serviceLocator.getLocalHome(
JNDINames.USERSGROUPS_HOME);
roleManagerLocalHome = (RoleManagerLocalHome)
serviceLocator.getLocalHome( JNDINames.ROLEMANAGER_HOME);
} catch (Exception ex) {
logger.error("Service Locate error:" + ex);
throw new CreateException();
}
}
//从Sequence EJB组件获得自增的序列号
public int getNewId(String name) {
try {
SequenceGeneratorLocal seq = sequenceHome.create();
return seq.nextSequenceNumber(name);
} catch (Exception ex) {
throw new EJBException("Error generating id for : " + name + ex);
}
}
//创建一个用户
public void createUser(UserEvent userEvent) {
User userDTO = userEvent.getUser();
if (nameIsExisted(userDTO)){
logger.debug("name :" + userDTO.getName() + " has been exsited");
userEvent.setErrors(Constants.NAME_EXISTED);
return;
}
if (emailIsExisted(userDTO)){
logger.debug("eamil :" + userDTO.getEmail() + " has been exsited");
userEvent.setErrors(Constants.EMAIL_EXISTED);
return;
}
UserLocal userLocal = null;
try {
String id = Integer.toString(getNewId(Constants.SEQUENCE_USER_NAME));
userLocal = userHome.create(id);
userDTO.setUserId(id);
updateUser(userEvent);
//创建该用户的默认角色
createUsersRoles(userDTO);
} catch (Exception ex) {
logger.error(ex);
throw new EJBException("create user error : " + ex);
}
}
//检查是否有重复用户名
private boolean nameIsExisted(User userDTO) {
boolean found = false;
User user = getUserByName(userDTO.getName());
if (user != null)
found = true;
return found;
}
//检查是否有重复E-mail
private boolean emailIsExisted(User userDTO) {
boolean found = false;
User user = getUserByEmail(userDTO.getEmail());
if (user != null)
found = true;
return found;
}
//创建用户默认角色,也就是建立用户和角色的对应关系
public void createUsersRoles(User userDTO) throws Exception {
UsersRoles usersRoles = null;
try {
RoleManagerLocal roleManagerLocal = roleManagerLocalHome.create();
String roleId = roleManagerLocal.getRoleId(Role.USER);
usersRoles = usersRolesHome.create(userDTO.getUserId(), roleId);
} catch (Exception ex) {
logger.error(ex);
throw new EJBException("createUsersRoles error : " + ex);
}
}
//修改用户资料
public void updateUser(UserEvent userEvent) {
User userDTO = userEvent.getUser();
UserLocal userLocal = null;
try {
userLocal = userHome.findByPrimaryKey(userDTO.getUserId());
userLocal.setName(userDTO.getName());
userLocal.setEmail(userDTO.getEmail());
userLocal.setPassword(userDTO.getPassword());
} catch (Exception ex) {
logger.error(ex);
}
}
//以E-mail获得用户
public User getUserByEmail(String emailAddress) {
emailAddress = emailAddress.trim().toLowerCase();
UserLocal userLocal = null;
try {
userLocal = userHome.findByEmail(emailAddress);
} catch (Exception ex) {
logger.warn(ex);
}
return getUser(userLocal);
}
//以Id查询用户
public User getUserById(String userId) {
userId = userId.trim().toLowerCase();
logger.debug(" userId =" + userId);
UserLocal userLocal = null;
try {
userLocal = userHome.findByPrimaryKey(userId);
} catch (Exception ex) {
logger.warn(ex);
}
return getUser(userLocal);
}
//获得用户User实例 DTO模式
private User getUser(UserLocal userLocal) {
if (userLocal == null)
return null;
logger.debug(" userId =" + userLocal.getUserId());
User user = new UserModel(userLocal.getUserId());
user.setEmail(userLocal.getEmail());
user.setName(userLocal.getName());
user.setPassword(userLocal.getPassword());
user.setUserId(userLocal.getUserId());
return user;
}
//获得用户的Principal
public User getUserByPrincipal() throws Exception, PrincipalException {
Principal principal = null;
try {
principal = sessionContext.getCallerPrincipal();
} catch (Exception e) {
logger.error(e);
throw new PrincipalException();
}
if (principal == null) {
throw new PrincipalException();
}
String name = principal.getName();
return getUserByName(name);
}

}
在UserManager中基本实现了用户资料的相关操作。在本项目中,还有用户组以及角色的相关操作。为避免UserManager中包含过多逻辑,需要再建立一个无状态Session Bean,如RoleManager。
用户注册登录后,其Session生存周期的活动将一直维持到其离开系统。因此可以建立一个有状态Session Bean保存用户的资料如User实例,这样不必经常到数据库读取。有状态Session Bean还可以作为一个总的Facade类,分别包含其他Facade群,这样,系统会显得条理分明。创建有状态Session Bean代码如下:
public class SecurityFacadeBean extends EJBControllerBean {
private final static Logger logger = Logger.getLogger(SecurityFacadeBean.class);
SessionContext sessionContext;
RoleManagerLocalHome roleManagerLocalHome;
UserManagerLocalHome userManagerLocalHome;
AsyncSenderLocalHome asyncSenderLocalHome;
private User user = null;

//获得Facade类 UsermanagerLocal
public UserManagerLocal getUserManager() {
UserManagerLocal userManagerLocal = null;
try {
userManagerLocal = userManagerLocalHome.create();
} catch (Exception ex) {
logger.error("getUserManager() error:" + ex);
}
return userManagerLocal;
}
//获得Facade类RoleManagerLocal
public RoleManagerLocal getRoleManager() {
RoleManagerLocal roleManagerLocal = null;
try {
roleManagerLocal = roleManagerLocalHome.create();
} catch (Exception ex) {
logger.error("getRoleManager() error:" + ex);
}
return roleManagerLocal;
}
//获得当前session的User实例
public User getUser() {
if (this.user != null)
return this.user;
logger.debug("user is null, get it from principal ...");
setUser();
return this.user;
}
//密码丢失查询
public boolean getPassword(String email) {
logger.debug("--> enter getpassword");
boolean success = false;
try {
User user = getUserManager().getUserByEmail(email);
if (user == null)
return success;
String subject = " 用户名和密码";
StringBuffer buffer = new StringBuffer();
buffer.append(" 用户:").append(user.getName());
buffer.append(" 密码:").append(user.getPassword());
if (sendMail(user.getEmail(), subject, buffer.toString()))
success = true;
} catch (Exception ex) {
logger.error(" getPassword: " + ex);
}
return success;
}
//调用E-mail发送组件,通过JMS发出E-mail
public boolean sendMail(String toAddress, String subject, String content) {
try {
logger.debug(" -->enter send mail");
Mail mail = new Mail();
mail.setToAddress(toAddress);
mail.setFromAddress("banq@jdon.com");
mail.setSubject(subject);
mail.setContent(content);
String msg = MailUtil.getMsgFromMail(mail);
AsyncSenderLocal asyncSenderLocal = asyncSenderLocalHome.create();
asyncSenderLocal.sendAMessage(msg);
logger.debug(" -->send mail to: " + toAddress + " successfully!");
return true;
} catch (Exception ex) {
logger.error(" sendMail() error : " + ex);
return false;
}
}
//判断当前用户是否是管理员
public boolean isAdministrator() {
return sessionContext.isCallerInRole(Role.ADMINISTRATOR);
}
//判断当前用户是否是普通用户
public boolean isUser() {
return sessionContext.isCallerInRole(Role.USER);
}

}
可以看到,SecurityFacadeBean是继承接口框架系统中的EJBController,SecurityFacadeBean作为一个总的Facade类,通过接口框架负责和Web实现联系,如图6-6所示。

图6-6 EJB Facade群
SecurityFacadeBean中isCallerInRole是用来判断当前用户是否属于可以访问该资源的角色,访问该资源角色的权限在ejb-jar.xml中定义。
3.5 EJB容器安全配置
J2EE容器的安全管理框架以角色为联系纽带,分两个方向。一个是用户资料系统;另外一个是访问权限系统。后者是通过web.xml或ejb-jar.xml配置实现的。
在本项目中,为了限制角色对某些类或方法的访问权限,可以在ejb-jar.xml中设置。

the role is super user
Admin

register user
User

User

RoleManager
*



在本EJB中定义了两个角色:Admin和User(当然可以根据实际情况定义角色)。具体的权限是在method-permission中设置,可以指定某个类的具体方法,“*”表示所有的方法。
既然定义了角色,那么在具体类的配置中也需要声明具体角色的指向,如SecurityFacadeBean的ejb-jar配置如下:

SecurityFacade
SecurityFacade

com.jdon.security.auth.ejb.SecurityFacadeLocalHome

com.jdon.security.auth.ejb.SecurityFacadeLocal
com.jdon.security.auth.ejb.SecurityFacadeBean
Stateful
Containerejb/UserManager
Session

com.jdon.security.auth.ejb.UserManagerLocalHome

com.jdon.security.auth.ejb.UserManagerLocal
UserManager

ejb/RoleManager
Session

com.jdon.security.auth.ejb.RoleManagerLocalHome

com.jdon.security.auth.ejb.RoleManagerLocal
RoleManager

ejb/AsyncSender
Session

com.jdon.asyncsender.ejb.AsyncSenderLocalHome

com.jdon.asyncsender.ejb.AsyncSenderLocal
AsyncSender

User
User

在最后几行,指定角色名User指向了security-role中的User,两者名称可以不一样,但role-link必须和security-role是一致的。role-name可以是RegisterUser,那么在SecurityFacadeBean调用就是isCallerInRole("RegisterUse"),检查当前访问SecurityFacadeBean的角色是否是容器中已经定义的、允许访问的角色RegisterUse。
通过ejb-jar.xml的配置,使得角色的访问权限的设置和管理变得非常方便。在系统运行时,有可能有新的角色加入或原有角色权限的修改。这些都可以通过ejb-jar.xml进行修改,修改完毕,运行中的J2EE系统立即生效。
具体ejb-jar.xml的权限配置可参考Sur公司EJB 标准手册。
为了使该EJB的权限机制激活,还需要在相应的容器配置文件中进行适当配置。如在JBoss中部署该EJB时,需要在jboss.xml加入:
java:/jaas/SecurityRealm
这表示该EJB的安全域将使用JAAS中的SecurityRealm配置。关于如何设置JBoss的SecurityRealm将在部署配置
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: