上一级 首页 下一级
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);
}
用户组是用来代表一组用户,可以指定一个用户组为一个特定角色,那么该用户组下的所有用户也将拥有该角色的访问权限能力。
用户和角色的直接关系设定为多对一,每个用户只能为一个角色,这样可以使问题变得简单些。
依据业务对象模型建立相应的数据模型,同时使用专门关联表来实现业务对象之间的关系。在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为:
<entity>
<display-name>User</display-name>
<ejb-name>User</ejb-name>
<local-home>com.jdon.security.auth.ejb.UserHome</local-home>
<local>com.jdon.security.auth.ejb.UserLocal</local>
<ejb-class>com.jdon.security.auth.ejb.UserBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>User</abstract-schema-name>
<cmp-field>
<field-name>userId</field-name>
</cmp-field>
<cmp-field>
<field-name>password</field-name>
</cmp-field>
<cmp-field>
<field-name>name</field-name>
</cmp-field>
<cmp-field>
<field-name>email</field-name>
</cmp-field>
<primkey-field>userId</primkey-field>
<query>
<query-method>
<method-name>findByEmail</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<ejb-ql>SELECT
OBJECT(s) FROM User AS s WHERE s.email=?1</ejb-ql>
</query>
<query>
<query-method>
<method-name>findByName</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<ejb-ql>SELECT
OBJECT(s) FROM User AS s WHERE s.name=?1</ejb-ql>
</query>
</entity>
在该实体bean中,使用EJB-QL实现了以E-mail或用户名为关键字的查询语句。
其他数据表都可以采取类似上述方法建立,当然使用Jbuilder专门的EJB可视化开发工具,可以直接从数据库中将这些表自动导入成相应的实体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中设置。
<assembly-descriptor>
<security-role>
<description>the
role is super user </description>
<role-name>Admin</role-name>
</security-role>
<security-role>
<description>register
user</description>
<role-name>User</role-name>
</security-role>
<method-permission>
<role-name>User</role-name>
<method>
<ejb-name>RoleManager</ejb-name>
<method-name>*</method-name>
</method>
</method-permission>
…
</assembly-descriptor>
在本EJB中定义了两个角色:Admin和User(当然可以根据实际情况定义角色)。具体的权限是在method-permission中设置,可以指定某个类的具体方法,“*”表示所有的方法。
既然定义了角色,那么在具体类的配置中也需要声明具体角色的指向,如SecurityFacadeBean的ejb-jar配置如下:
<session>
<display-name>SecurityFacade</display-name>
<ejb-name>SecurityFacade</ejb-name>
<local-home>
com.jdon.security.auth.ejb.SecurityFacadeLocalHome
</local-home>
<local>com.jdon.security.auth.ejb.SecurityFacadeLocal</local>
<ejb-class>com.jdon.security.auth.ejb.SecurityFacadeBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Container</transaction-type>
<ejb-local-ref>
<description
/>
<ejb-ref-name>ejb/UserManager</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>
com.jdon.security.auth.ejb.UserManagerLocalHome
</local-home>
<local>com.jdon.security.auth.ejb.UserManagerLocal</local>
<ejb-link>UserManager</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<description
/>
<ejb-ref-name>ejb/RoleManager</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>
com.jdon.security.auth.ejb.RoleManagerLocalHome
</local-home>
<local>com.jdon.security.auth.ejb.RoleManagerLocal</local>
<ejb-link>RoleManager</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<description
/>
<ejb-ref-name>ejb/AsyncSender</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local-home>
com.jdon.asyncsender.ejb.AsyncSenderLocalHome
</local-home>
<local>com.jdon.asyncsender.ejb.AsyncSenderLocal</local>
<ejb-link>AsyncSender</ejb-link>
</ejb-local-ref>
<security-role-ref>
<description
/>
<role-name>User</role-name>
<role-link>User</role-link>
</security-role-ref>
</session>
在最后几行,指定角色名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加入:
<security-domain>java:/jaas/SecurityRealm</security-domain>
这表示该EJB的安全域将使用JAAS中的SecurityRealm配置。关于如何设置JBoss的SecurityRealm将在部署配置相关章节中讨论。
上一级 首页 下一级
|