权限管理模块分析

巴巴运动网的后台权限管理模块主要采用了两大核心技术:java的反射和自定义注解
即是权限管理,让人很容易就想到过滤,也许也可以叫做权限过滤吧,即然是过滤,那就
总得有个过滤的粒度吧,根据不同的需求控制的粒度都是不同的,粗粒度的过滤
用我们最常用的过滤器对request对象里的请求url地址进行过滤即可,细粒度的过滤
甚至可以考虑将需要的每个方法里写死权限验证,当然适合需求即好.巴巴运动网采用
了用黎老师的话所说的最优雅,粗细粒度可控制,无侵入性.下面对此进行详细的介绍.
首先这里简单的介绍下整个权限管理模块的实体类的相关设计:
权限实体设计如下:
---------------------------------------------------------------------------------------------------------------------
/**
这里权限的id我们采用了联合主键,这样从面象对象的角度,它具有更直观,更合理的设计,联合主键类里有model(代表模块的名称),privilegeValue(操纵该模块所需要的权限值)
**/
private SystemPrivilegePK id;
private String name; //权限名称
---------------------------------------------------------------------------------------------------------------------
整个权限管理模块的设计似乎并没有考虑为每个用户单独分配权限,而是采用了权限组的
模式,先将一系列的权限添加到一个权限组,然后将相应的权限组分给相就的用户即可.
权限组的实体设计如下:
----------------------------------------------------------------------------------------------------------------------
private Integer id; //权限组的ID
private String name; //权限组的名称
存放该组所拥有的权限的set集合.
它与权限表是多对多的关系,即一个组里可以有多个权限,一个权限可以存在于
不同的组里.这里给出具体的多对多注解
该注解告诉jpa该集合用于多对多关系的映射,级联系关系为更新,强制不使用懒加载
@ManyToMany(cascade=CascadeType.REFRESH, fetch=FetchType.EAGER)
//该注解告诉jpa会生成一个中间表,表名为group_privilege,这里的joinColumns表示
生成的数据库表里将有名为group_id的字段,将且该字段为外键参考于该实体类,即该实体类所映射的数据库表的主键id,
@JoinTable(name="group_privilege",joinColumns=@JoinColumn(name="group_id"),
//inverseJoinColumns表时反向关联到该数据库表的字段,这里的反向指的就是对应的
SystemPrivilege实体类了,这里的两个joinColumn分别表示 生成的数据库表里将有名为
model的字段,其作为外键参考于SystemPrivilege实体类的主键id(默认外键参考于该实体类的id , 但 由于该id采用了联合主键,所以要具体指定参考了哪个字段)的model字段,名为privilegeValue的字段,其作为外键参考于SystemPrivilege实体类的privilegeValue字段.
inverseJoinColumns={@JoinColumn(name="model",referencedColumnName="model"),
@JoinColumn(name="privilegeValue",referencedColumnName="privilegeValue")})
private Set<SystemPrivilege> privileges = new HashSet<SystemPrivilege>();
----------------------------------------------------------------------------------------------------------------------
上面两个实体类即分别表示了权限,和权限组,接下来则是考虑如何设计整个权限模块了,首先当然是考虑的在哪一层加入权限控制,如果是在dao层,那么当用户请求到action,action调用相应的业务方法,业务方法可能涉及到数据库的增删改查,这时业务层里又会去调用dao层的方法来访问数据库,结果在这一层发现这个用户没有相应的权限,最终操纵失败!那么前面做的那么多全是多余的,这样的设计显然是非常不合理的,不难发现,权限的控制在越靠近前端就是越优的,我们把目标选在了action控制层.
经过上面的分析后,决定将权限的控制放在action这一层来实现,既然采用了spring,很自然就能想到spring的aop了,确实,它非常的复合这种应用模式,那么问题又来了,我们应该以什么样的方式来告诉程序,要有什么样的权限才能调用相应的action的方法呢,不可能为每个action的方法在数据库里有条记录标记上调用该方法要什么什么样的权限,这样的做法非常的不直观,维护性也是很差的,幸好java为我们提供了很多有用的技术,这里我们采用了注解,在相应的方法上注解上调用该方法所必要的权限,这样在有用户试图调用该方法时,aop先拦截到该次方法的调用,判断该方法上是否存在相应的注解,如不存在则该方法是共公的,如存在相应的注解,则通用java为我们提供的反射来取得该注解,并得到注解上的值来进行更多的相应的处理,看起来一切都规划的非常好,天衣无缝,那我们就先从这个自定义注解开始吧.
-------------------------------------------------------------------------------------------------------------------
自定义注解类的设计:
//该注解表示该注解在程序运行期会被处理
@Retention(RetentionPolicy.RUNTIME)
//该注解表示该注解只能标识在方法上
@Target(ElementType.METHOD)
public@interfacePrivilege {
//表示该自定义注解里的元素,该元素表示模块名
String model();
//该元素表示操纵该模块名所要的权限值
String privilegeValue();
}

设计好自定义注解后,接下来要做的就是把需要权限的action的相应的方法都加上相应的自定义注解了。例:这里即表示该方法属于brand(品牌)模块,访问该方法需要view权限值
@Override@Privilege(model="brand", privilegeValue="view")
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
}
做好这一些就可以开始配置spring的AOP了.最终会发现spring的aop是拦截不到dispatchAction里的所有方法的,经过黎老师的仔细研究才发现原来ActionServlet对dispatchAction里的方法调用采用了java里的反射,spring的AOP拦截不到,它只能拦截到基于action的execute方法.于是最终放弃了spring的AOP(这里也告诉了我们,处事要灵活,没必要死扣).spring的AOP是在目标方法被调用之前进行拦截,拦截之后可以在目标方法调用之前和之后做自己想做的处理,基于这个思想,我们只要找出是谁调用的我们写在action和dispatchAction里的方法的那个控制器即可。由于整个项目采用了struts+spring+jpa+hibernate,并用spring的控制器替换了struts自己的控制器来完成struts和spring的整合,所以我们要找的那个控制器应该是spring的DelegatingRequestProcessor类,黎老凭多年的工作经验很肯定的就告诉了我们,DelegatingRequestProcessor类里的processActionPerform方法就是用来负责调用action和dispatchAction里的相应的方法的,我们只须要自己写个类,继承DelegatingRequestProcessor,并复盖processActionPerform,重点是要在struts-config.xml配置文件里把控制器换成我们现在这个自己写的类.由于在该方法里提供给了我们HttpServletRequest request,
HttpServletResponse response, Action action, ActionForm form,
ActionMapping mapping五个形参对象,那么我们可以先判断这里的action对象它是继承自Action还是dispatchAction,如果它是继承自Action,则根据Action类的特性,它只存在唯一的动作(方法),即execute,如果它继承自DispatchAction,那么根据访问dispatchActon的特性,用户的访问路径里一定带有要访问的具体的方法名,所以我们可以根据request.getParameter(mapping.getParameter());从request域里拿到用户试图调用的方法名,确定了用户将试图调用的方法名后,接下来的事就变的简单了,我们首先尝试通过方法名,和固定的形参通过该action返回用户试图调用的具体方法;
//注意这里使用的方法是Action.getClass().getDeclaredMethod()
//它可以获取包括private在内的方法.
method = Action.getClass().getDeclaredMethod(methodname,ActionMapping.class, ActionForm.class,HttpServletRequest.class,HttpServletResponse.class);
顺利得到该方法后,我们就可以通用method.isAnnotationPresent(Privilege.class)来判断该方法上是否存在我们预先定义好的注解,如果不存在,则该action或dispatchAction里的方法为共公的,我们这时候则可以直接调用DelegatingRequestProcessor类的super().processActionPerform方法,让程序正确的执行,如果存在我们预先定义的注解,我们首先返回该方法上的注解,然后得到该注解上我们定义的model和privilegeValue的具体值,然后我们通过这两个值可以构造出一个SystemPrivilegePK对象,忘了介绍,该对象即为权限的联合主键id类.到了这一步整个权限模块几乎完工了,如果用户正确登录该系统,我们会将用户的信息存入session内,在这里我们都构造出了SystemPrivilegePK对象,接下来只需从session拿到当前访问该方法的用户,并且返回该用户所在的权限组,然后遍历整个权限组,一一判断权限组里是否有权限的id(即联合主键)与我们通过注解构造出来的id相equals的(这里特别要注意, SystemPrivilegePK,是要重写hashCode()和equals方法的,具体实现见源码),如果存在equals,则说明该用户是有权限访问的,则调用DelegatingRequestProcessor类的super().processActionPerform方法让程序正确执行,如果不存在该权限id,说明该用户并没有权限访问,则程序直接跳转到相应的提示页面.致此整个权限大致流程介绍完了,写的非常乱,希望对整个巴巴运动网权限这一块不太明白的同学有一点点帮助吧,自己能力也有限,如果有时间,会在完善一下分析


在这给出我今天上午写好的关于注解权限过滤的代码,性能很差的,只是实现了功能:
PrivilegeDelegatingProcessor.java:
------------------------------------------------
package com.iwtxokhtd.news.bean;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.struts.DelegatingRequestProcessor;

import com.iwtxokhtd.news.bean.employee.Employee;
import com.iwtxokhtd.news.bean.privilege.PrivilegeGroup;
import com.iwtxokhtd.news.bean.privilege.SystemPrivilege;
import com.iwtxokhtd.news.bean.privilege.SystemPrivilegePK;
import com.iwtxokhtd.news.service.employee.EmployeeDAO;
import com.iwtxokhtd.news.utils.SiteUrl;
import com.iwtxokhtd.news.web.action.privilege.Privilege;

public class PrivilegeDelegatingRequestProcessor extends
DelegatingRequestProcessor {
private static ApplicationContext ctx;
private static EmployeeDAO employeeDao;

public PrivilegeDelegatingRequestProcessor(){
ctx=new ClassPathXmlApplicationContext("beans.xml");
employeeDao=(EmployeeDAO)ctx.getBean("employeeDAOBean");
}
@Override
protected ActionForward processActionPerform(HttpServletRequest request, HttpServletResponse response, Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException {
String methodname="";
if(mapping.getParameter()==null||"".equals(mapping.getParameter())){
methodname="execute";
}else{
methodname=request.getParameter(mapping.getParameter());
}
Method method=null;
try {
method=action.getClass().getDeclaredMethod(methodname, ActionMapping.class,ActionForm.class,HttpServletRequest.class,HttpServletResponse.class);
Boolean flag=method.isAnnotationPresent(Privilege.class);
Privilege priv=method.getAnnotation(Privilege.class);
//如果不存在注解,则是公共的方法,可以执行下去
if(!flag){
return super.processActionPerform(request, response, action, form, mapping);
}else{
//就是设置了权限的方法
//取得注解中的值
String model=priv.model();
String privilegeValue=priv.privilegeValue();
//构造权限主键
SystemPrivilegePK privilegepk=new SystemPrivilegePK(model,privilegeValue);
//取得登录的用户名
String username=(String) request.getSession().getAttribute("username");
//根据用户名取得员工实体对象
Employee employee=null;
if(username!=null&&!"".equals(username)){
employee=employeeDao.find(Employee.class, username);
}
SystemPrivilegePK dbpk=null;
Set<SystemPrivilegePK> privset=new HashSet<SystemPrivilegePK>();
//打印该员工的权限
for(PrivilegeGroup group:employee.getPrivilegegroups()){
for(SystemPrivilege syspriv:group.getPrivileges()){
//根据数据库中的权限模块和权限值构造权限主键
dbpk=new SystemPrivilegePK(syspriv.getPkid().getModel(),syspriv.getPkid().getPrivilegeValue());
if(!privset.contains(dbpk)){
privset.add(dbpk);
}
}
}
if(privset.contains(privilegepk)){
return super.processActionPerform(request, response, action, form, mapping);
}else{
request.setAttribute("message", "你没有此权限,请与管理员联系!");
request.setAttribute("urladdress", SiteUrl.readUrl("employee.has.no.privileges"));
return mapping.findForward("noprivilege");
}
}
}catch(NoSuchMethodException e){
e.printStackTrace();
}
return super.processActionPerform(request, response, action, form, mapping);
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值