权限系统是稍成规模的系统中一个必不可少的部分,操作的可否执行,流程的具体走向,业务的具体步骤都会涉及权限的处理。
具体来说有两种权限的控制方式:一种是
等级权限控制方式,一种是
角色权限控制方式。前者可用于范围控制,适用于用户权力大小不同的场合;后者可用于单点控制,适用于用户权力多寡有异的场合。现实世界中,军队中官衔类似于等级权限控制,现代企业中各司其职的权力分配类似于角色控制。范围控制前面已经提到过了,今天来谈谈角色权限控制。
角色权限控制是把单项权限一项项的赋予用户,如同现实世界中把具体职位一个个的赋予某个员工一样。在他执行操作前,先看他是否拥有执行此操作的权限,如果有则执行,否则不执行。
在这里我们还是采用上一讲的业务,实现IDocService的实现类DocService,但要把等级权限控制方式修改成角色权限控制方式,原来的处置是用户权限高于某个值就能执行操作,现在如果一个用户有“添加”角色,他就可以添加文档;如果他缺乏“修改”角色,他就不能修改文档。
实现用户角色权限控制并不复杂,下面请看具体思路:
1.创建两个领域对象类:用户类User和角色类Role,他们之间是一对多的关系,他们对应的表用主外键关联起来。使用Hibernate很容易实现这一关系。
2.使用AOP,实现IDocService接口的实现类DocService的代理,使用UserRoleController作为前置通知,角色权限控制放入其中。
3. UserRoleController中,查看用户是否有执行某项操作的权限,没有则抛出异常NoRoleException,否则执行的DocService中的函数。
领域对象类的基类BaseDomainObj,User,Role,Doc等都是它的子类。
package com.heyang.domain;


/** *//**
* 领域对象基类
* @author 何杨
* @version 1.00
* @since 2009-1-5 上午10:26:32
*
*/

public abstract class BaseDomainObj
{
// ID
protected long id;
// 名称
protected String name;

public String toString()
{
return name;
}


public long getId()
{
return id;
}


public void setId(long id)
{
this.id = id;
}


public String getName()
{
return name;
}


public void setName(String name)
{
this.name = name;
}
}
User类:
package com.heyang.domain;

import java.util.LinkedHashSet;
import java.util.Set;


/** *//**
* 领域对象用户类
* @author 何杨
* @version 1.00
* @since 2009-1-5 上午10:23:25
*
*/

public class User extends BaseDomainObj
{
// 用户所拥有的权限
private Set<Role> roles=new LinkedHashSet<Role>();

public User()
{
}

public User(String name)
{
this.name=name;
}

/** *//**
* 判断用户是否拥有角色。若参数角色名在用户角色集合中能找到则认为用户有此角色,即名相同则有,否则无
* @param roleName :角色名
* @return
*/

public boolean hasRole(String roleName)
{

for(Role role:roles)
{

if(role.getName().equals(roleName))
{
return true;
}
}
return false;
}


public Set<Role> getRoles()
{
return roles;
}


public void setRoles(Set<Role> roles)
{
this.roles = roles;
}
}
User类的Hibernate映射文件,Set部分是关键:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.auction">
<class name="com.heyang.domain.User"
table="AOPRoleSample_User" lazy="false">
<id name="id" column="ID" >
<generator class="native"/>
</id>
<property name="name" column="name" />
<set name="roles" cascade="all" lazy="false">
<key column="userid"/>
<one-to-many class="com.heyang.domain.Role"/>
</set>
</class>
</hibernate-mapping>

Role类:
package com.heyang.domain;


/** *//**
* 领域对象角色类
* @author 何杨
* @version 1.00
* @since 2009-1-5 上午10:32:16
*
*/

public class Role extends BaseDomainObj
{

public Role()
{
}

public Role(String name)
{
this.name=name;
}
}

Role类的映射文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.auction">
<class name="com.heyang.domain.Role"
table="AOPRoleSample_Role" lazy="false">
<id name="id" column="ID" >
<generator class="native"/>
</id>
<property name="name" column="name" />
</class>
</hibernate-mapping>

DocService类,有了AOP的帮忙,其中不需要任何权限控制的代码,它甚至不知道权限控制子系统的存在:

public class DocService extends BaseService implements IDocService
{

/** *//**
* 按ID取得文档
* @param id
* @return
*/

public Doc getDoc(long id)
{
return (Doc)dao.get(Doc.class,id);
}

public void add(Doc doc, User user)
{
System.out.println("将" + doc + "交由dao处理(存入数据库)");
dao.create(doc);
}


public void delete(Doc doc, User user)
{
System.out.println("将" + doc + "交由dao处理(从数据库删除)");
dao.delete(doc);
}


public void update(Doc doc, User user)
{
System.out.println("将" + doc + "交由dao处理(修改数据库中对应的记录)");
dao.update(doc);
}
}

UserRoleController类,它作为DocService的前置处理器,在真正的数据库操作开始前进行权限处理:
package com.heyang.service;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

import com.heyang.domain.User;
import com.heyang.exception.NoRoleException;


/** *//**
* 实现角色子系统---用户角色控制
* @author: 何杨(heyang78@gmail.com)
* @date: 2009-1-2-下午04:19:13
*/

public class UserRoleController implements MethodBeforeAdvice
{
private String addDocRoleName;
private String deleteDocRoleName;
private String updateDocRoleName;

/** *//**
* 在IDocService的实际方法开始前进行前置处理--用户角色检查
*/

public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable
{
// 取得方法名
String mothodName=arg0.getName();
// 取得用户
User user=null;

if(arg1.length>1)
{
user=(User)arg1[1];
}
// 根据方法名判断用户是否拥有所需要的角色,否则抛出异常

if("add".equals(mothodName))
{

if(user.hasRole(addDocRoleName)==false)
{
throw new NoRoleException("用户"+user+"必须拥有‘添加’角色才能执行添加文档操作");
}
}

else if("delete".equals(mothodName))
{

if(user.hasRole(deleteDocRoleName)==false)
{
throw new NoRoleException("用户"+user+"必须拥有‘删除’角色才能执行删除文档操作");
}
}

else if("update".equals(mothodName))
{

if(user.hasRole(updateDocRoleName)==false)
{
throw new NoRoleException("用户"+user+"必须拥有‘修改’角色才能执行修改文档操作");
}
}
}



public String getAddDocRoleName()
{
return addDocRoleName;
}



public void setAddDocRoleName(String addDocRoleName)
{
this.addDocRoleName = addDocRoleName;
}



public String getDeleteDocRoleName()
{
return deleteDocRoleName;
}



public void setDeleteDocRoleName(String deleteDocRoleName)
{
this.deleteDocRoleName = deleteDocRoleName;
}



public String getUpdateDocRoleName()
{
return updateDocRoleName;
}



public void setUpdateDocRoleName(String updateDocRoleName)
{
this.updateDocRoleName = updateDocRoleName;
}
}
全体配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="org.gjt.mm.mysql.Driver">
</property>
<property name="url" value="jdbc:mysql://127.0.0.1/test">
</property>
<property name="username" value="root"></property>
<property name="password" value="hy"></property>
</bean>
<!-- Hibernate Session Factory,使用了上面配置的数据源 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="mappingResources">
<list>
<value>com/heyang/domain/User.hbm.xml</value>
<value>com/heyang/domain/Role.hbm.xml</value>
<value>com/heyang/domain/Doc.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.hbm2ddl.auto=Acreate
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.format_sql=true
</value>
</property>
</bean>
<!-- Hibernate Template,使用了上面配置的sessionFactory -->
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<!-- 使用了上面配置的hibernateTemplate的BaseDao -->
<bean id="dao" class="com.heyang.dao.BaseDao">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate"/>
</property>
</bean>
<!-- 使用了上面配置的dao的UserService -->
<bean id="userService" class="com.heyang.service.UserService">
<property name="domainClass">
<value>User</value>
</property>
<property name="dao">
<ref bean="dao"/>
</property>
</bean>
<!-- 用于文件处理的IDocService实现类DocService -->
<bean id="docService" class="com.heyang.service.DocService">
<property name="dao">
<ref bean="dao"/>
</property>
</bean>
<!-- 在执行docService的实际方法前进行用户角色检查 -->
<bean id="userRoleController" class="com.heyang.service.UserRoleController">
<property name="addDocRoleName" value="添加" />
<property name="deleteDocRoleName" value="删除" />
<property name="updateDocRoleName" value="修改" />
</bean>
<!-- docService的代理对象 -->
<bean id="docServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.heyang.service.IDocService</value>
</property>
<property name="interceptorNames">
<list>
<value>userRoleController</value>
</list>
</property>
<property name="target">
<ref bean="docService"/>
</property>
</bean>
</beans>

模拟处理:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=(UserService)ctx.getBean("userService");
IDocService docService=(IDocService)ctx.getBean("docServiceProxy");
User user=userService.getUser(1);
Doc doc1;
// 模拟添加操作
doc1=new Doc("论次贷危机对美国的影响");

try
{
docService.add(doc1, user);
}

catch(NoRoleException ex)
{
ex.printStackTrace();
}
// 模拟修改操作
doc1=docService.getDoc(1);
doc1.setName("论次贷危机对中国的影响");

try
{
docService.update(doc1, user);
}

catch(NoRoleException ex)
{
ex.printStackTrace();
}
// 模拟删除操作
doc1=docService.getDoc(1);
doc1.setName("论次贷危机对世界的影响");

try
{
docService.delete(doc1, user);
}

catch(NoRoleException ex)
{
ex.printStackTrace();
}
小结:
权限子系统是企业应用中不可或缺的环节,它具体的权限控制方式有两种:一种是等级权限控制方式,一种是角色权限控制方式,其他复杂的权限系统都可以由它们组合而来。
由于业务控制的关系,权限子系统和其他业务子系统的最容易耦合在一起,久而久之会对程序的可读性,可维护性带来消极影响,而AOP恰好能帮助我们有效降低权限子系统和其他业务子系统的耦合,实现他们之间的离散化。因此,AOP值得我们认真掌握,尤其是其背后面向方面编程的精神。
代码下载:
http://www.blogjava.net/Files/heyang/AOPRoleSample20090106060255.rar
需要的库请自行补充(基本ProjectTodolist的lib全有)。