`
xxp3369
  • 浏览: 148003 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

权限管理模块分析

阅读更多
巴巴运动网的后台权限管理模块主要采用了两大核心技术: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);
}
}

分享到:
评论
1 楼 kljismi 2011-03-10  
你好,我现在正在开这项目的代码,但是我不明白
@Privilege(model="brand", privilegeValue="view")   brand和view这两个值是从那得来的,需要配置吗,还是存在数据库中哦,

相关推荐

Global site tag (gtag.js) - Google Analytics