以前做的电力系统,实现对用户的角色进行细颗粒度权限控制的设计,现在分享下:
先看数据表设计:
* 角色表:
* 用户角色中间表(多对多关系)
* 权限表:(这里使用ztree动态加载左侧菜单栏,只显示用户所具有的权限的菜单栏项。)
* 角色和权限中间表:
通过上面表结构,大家应该大概了解用户,角色,权限的大概关系。现在开始进行Struts2拦截器实现细颗粒权限控制
(1) 在util包下创建注解的类AnnotationLimit,用来控制在Action类中的方法。例如:在Action的方法上定义:@AnnotationLimit(mid="an",pid="am")
/**
* 自定义注解
*/
//被这个注解修饰的注解,利用反射,将其他的注解读取出来
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationLimit {
String mid(); //子模块模块名称
String pid(); //父模块操作名称
}
(2) 在util包下创建拦截器ErrorAndLimitInterceptor,代码如下:
@SuppressWarnings("serial")
public class ErrorAndLimitInterceptor extends MethodFilterInterceptor {
public void init() {
}
/**
* 过滤器过滤url(.do和.jsp)
* 拦截器拦截url(.do)
* actioninvocation.invoke():调用struts2的Action的方法,并返回String类型的对应的返回值
*/
public String doIntercept(ActionInvocation actioninvocation) {
//把自定义错误信息 放置到request中
HttpServletRequest request = (HttpServletRequest) actioninvocation
.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
try {
//获取请求的action对象
Object action = actioninvocation.getAction();
//获取请求的方法的名称
String methodName = actioninvocation.getProxy().getMethod();
//获取action中的方法的封装类(action中的方法没有参数)
Method method = action.getClass().getMethod(methodName, null);
String result = null; // Action的返回值
//在完成跳转Action之前完成细颗粒权限控制,控制Action的每个方法
//检查注解,是否可以操作权限的URL
boolean flag = isCheckLimit(request,method);
if(flag){
// 运行被拦截的Action,期间如果发生异常会被catch住
result = actioninvocation.invoke();
}
else{
request.setAttribute("errorMsg", "对不起!您没有权限操作此功能!");
return "errorMsg";
}
return result;
} catch (Exception e) {
/**
* 处理异常
*/
String errorMsg = "出现错误信息,请查看日志!";
//通过instanceof判断到底是什么异常类型
if (e instanceof RuntimeException) {
//未知的运行时异常
RuntimeException re = (RuntimeException) e;
//re.printStackTrace();
errorMsg = re.getMessage().trim();
}
/**
* 发送错误消息到页面
*/
request.setAttribute("errorMsg", errorMsg);
/**
* log4j记录日志
*/
Log log = LogFactory
.getLog(actioninvocation.getAction().getClass());
log.error(errorMsg, e);
return "errorMsg";
}// ...end of catch
}
public void destroy() {
}
/**验证细颗粒权限控制*/
public boolean isCheckLimit(HttpServletRequest request, Method method) {
if(method == null){
return false;
}
//获取当前的登陆用户
ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
if(elecUser == null){
return false;
}
//获取当前登陆用户的角色(一个用户可以对应多个角色)
Hashtable<String, String> ht = (Hashtable)request.getSession().getAttribute("globle_role");
if(ht == null){
return false;
}
//处理注解,判断方法上是否存在注解(注解的名称为:AnnotationLimit)
/*
* 例如:
* @AnnotationLimit(mid="aa",pid="0")
public String home(){
*/
boolean isAnnotationPresent = method.isAnnotationPresent(AnnotationLimit.class);
//不存在注解(此时不能操作该方法)
if(!isAnnotationPresent){
return false;
}
//存在注解(调用注解)
AnnotationLimit limit = method.getAnnotation(AnnotationLimit.class);
//获取注解上的值
String mid = limit.mid(); //权限子模块名称
String pid = limit.pid(); //权限父操作名称
/**
* 如果登陆用户的角色id+注解上的@AnnotationLimit(mid="aa",pid="0")
* * 在elec_role_popedom表中存在 flag=true,此时可以访问Action的方法;
* * 在elec_role_popedom表中不存在 flag=false,此时不能访问Action的方法;
*/
boolean flag = false;
//拦截器中加载spring容器,从而获取Service类,使用Service类查询对应的用户信息
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
IElecRoleService elecRoleService = (IElecRoleService)wac.getBean(IElecRoleService.SERVICE_NAME);
//遍历角色ID
if(ht!=null && ht.size()>0){
for(Iterator<Entry<String, String>> ite = ht.entrySet().iterator();ite.hasNext();){
Entry<String, String> entry = ite.next();
//获取角色ID
String roleID = entry.getKey();
flag = elecRoleService.findRolePopedomByID(roleID, mid, pid);
if(flag){
break;
}
}
}
return flag;
}
}
(3) 在ElecRoleService类下(即角色模块service)创建新增方法,使用角色ID,权限code,父级权限code作为联合主键查询角色权限表,判断当前用户是否可以访问该操作。
/**使用角色ID,子权限编号,父权限编号,查询角色权限表的所有数据*/
public boolean findRolePopedomByID(String roleID,String mid,String pid) {
//组织查询条件
String condition = "";
List<Object> paramsList = new ArrayList<Object>();
//角色ID
if(StringUtils.isNotBlank(roleID)){
condition += " and o.roleID = ?";
paramsList.add(roleID);
}
//子权限名称
if(StringUtils.isNotBlank(mid)){
condition += " and o.mid = ?";
paramsList.add(mid);
}
//父权限名称
if(StringUtils.isNotBlank(pid)){
condition += " and o.pid = ?";
paramsList.add(pid);
}
Object [] params = paramsList.toArray();
//查询对应的角色权限信息
List<ElecRolePopedom> list = elecRolePopedomDao.findCollectionByConditionNoPage(condition, params, null);
boolean flag = false;
if(list!=null && list.size()>0){
flag = true;
}
return flag;
}
(4) 在struts.xml中定义自定义拦截器:放置在package下
<interceptors>
<!-- 声明拦截器 -->
<interceptor name="errorAndLimitInterceptor" class="cn.best.elec.util.ErrorAndLimitInterceptor" />
<!-- 配置拦截器栈 -->
<interceptor-stack name="myErrorAndLimitInterceptor">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="errorAndLimitInterceptor">
<!-- 配置哪些方法不被拦截器 -->
<param name="excludeMethods">menuHome,title,left,change,loading,logout,alermStation,alermDevice,showMenu</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 覆盖底层的拦截器栈 对包中的所有action都有效 -->
<default-interceptor-ref name="myErrorAndLimitInterceptor"/>
<!-- 全局返回 -->
<global-results>
<result name="close">/close.jsp</result>
<result name="errorMsg">/errorMsg.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping result="errorMsg" exception="java.lang.Exception"></exception-mapping>
</global-exception-mappings>
(5) 在Action的方法上定义:
@AnnotationLimit(mid="an",pid="am")
public String home(){
}
大功告成。