使用拦截器或者AOP实现权限管理(OA系统中实现权限控制)

时间:2020-12-27 16:20:06

在开发类似与OA管理系统类型的项目中,经常需要设置到权限管理。例如对某个部门的人员CURD操作,默认是该部门的普通员工是不会有该权限的。但若某个员工升级为该部分的负责人。则此时它就拥有对该部门的CURD操作,此时管理员就应该对该员工赋予该权限。这篇文章教大家如果处理该需求,即权限管理。
首先应该明白,在权限管理中,权限的控制对象是方法:
在我们J2EE开发过程中,实现权限的控制主要有两种方法:
第一种方式:
利用struts2的拦截器
第二种方式:
利用spring的AOP技术
我们一一介绍这两种方法:

1.使用struts2拦截器来判断

现在我们看刚刚的需求,其中涉及到了三个主体:
1.用户user(该部门的所有用户)
2.角色Role(普通员工or部门负责人)
3.权限Privilege(关于对该部分CURD的权限)

关于这三个主体之间的对应关系,大家自己模拟一下(在实际项目中我们以实际的项目为准):

User与Role是多对多 Role与Privilege是多对多

编写javabean:

User类:

public class User implements Serializable{
private String uid;
private String username;
private String password;
private String sex;
private String phone;
private String email;
private Set<Role> roles;
//sette和getter方法为了节省空间此时不再写出....
...
}

Role:

public class Role implements Serializable{
private String rid;
private String name;
private String description;
private Set<User> users;
private Set<Privilege> privileges;
...
}

Privilege:

public class Privilege implements Serializable{
private Long id; //主键
private String name;
private String description;
private Set<Role> roles;
...
}

当User登录的时候,在执行登录的方法中根据此User的Role查询中其所有的权限:
把具有的权限集合放入session中。
实例代码:

public String login(){
User user = loginService.login(username, passwd);
if(user==null){
return "loginError";
}else{
//根据用户id查询出该用户能够访问到的权限(功能),把功能权限放入到session中
//权限需要在访问的不同的方法的时候不断的在拦截器中判断是否具有权限。所以放在session中
Collection<Privilege> privileges = this.privilegeService.getFunctionsByUid(user);
//把用户能够访问到的功能放入到session中
ServletActionContext.getRequest().getSession().setAttribute("functions",privileges);
ServletActionContext.getRequest().getSession().setAttribute("user", user);
return "loginSuccess";
}
}

如上的方法:

this.privilegeService.getFunctionsByUid(user);//是根据此User的Role查看所有的privilege。。方法比较简单,此处省略。

把用户的权限放入session之后,此时在登录成功的界面可以更加此权限的集合来动态的显示操作列表(这里我使用的jQuery的ztree插件)。
当然把用户的权限放入session的目的并不是仅仅在前台页面中获取,前面我们说过权限的控制最核心的是控制在方法上,当用户访问方法的时候即在在拦截器中判断是否具有权限。所以放在session中。

现在我们来回顾一下struts2的拦截器的用法:

在你访问action中的某个方法时,首先会被拦截器所执行,执行拦截器中的intercept方法。该方法的参数ActionInvocation是你访问的action的实例的代理对象,通过它我们可以获取你要请求的方法和请求action的class形式.

现在我们自定义一个注解,用来标识给方法来标识访问此方法应该具有什么权限:

@Target(ElementType.METHOD)  //注解作用的位置。。方法
@Retention(RetentionPolicy.RUNTIME) //注解的生命周期
public @interface PrivilegeInfo {
String name() default "";
}

写解析自定义注解内容的工具类:

public class PrivilegeInfoAnnotationParse {
public static String parse(Class targetClass,String methodName) throws NoSuchMethodException, SecurityException{
String privilegeName = "";
Method method = targetClass.getMethod(methodName);
//该方法上存在PrivilegeInfo注解
if(method.isAnnotationPresent(PrivilegeInfo.class)){
PrivilegeInfo privilegeInfo = method.getAnnotation(PrivilegeInfo.class);
privilegeName = privilegeInfo.name();
}
return privilegeName;
}
}

在action的中的方法中加上该注解:

    @PrivilegeInfo(name="权限A")
public String showData() {
...
}

自定义一个拦截器:

@Component(value="myIntercept")
public class MyIntercept implements Interceptor {

@Override
public void destroy() {
// TODO Auto-generated method stub

}

@Override
public void init() {
// TODO Auto-generated method stub

}

@Override
public String intercept(ActionInvocation invocation) throws Exception {
/**
* 1、获取用户能够访问到的功能权限
* 2、获取访问当前请求的方法中的权限
* 3、查看当前用户的权限是否包含当前请求方法的权限
* 如果包含,则继续访问 如果不包含,则跳转到错误页面
*/

Collection<Privilege> privileges = (List<Privilege>) ServletActionContext.getRequest().getSession()
.getAttribute("functions");
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
if(user!=null&&privileges!=null){
// 获取当前请求的方法
String methodName = invocation.getProxy().getMethod();
// 当前请求action的class形式
Class targetClass = invocation.getAction().getClass();
// 访问当前方法应该具有的权限
String privilegeName = PrivilegeInfoAnnotationParse.parse(targetClass, methodName);
//是否通过访问
boolean flag = false;
if(privilegeName==null||privilegeName.equals("")){
flag=true; //没有权限,获取权限为""说明任何人都可以访问
}else if(user.getUsername().equals("admin")){
flag=true;
}else {
for(Privilege p : privileges){
if(p.getName().equals(privilegeName)){//用户能够访问的权限中包含有当前方法的访问权限
flag = true;
break;
}
}
}
if(flag){
return invocation.invoke();
}else{
ActionContext.getContext().getValueStack().push("权限不足");
return "error";
}
}
else{
return invocation.invoke();
}
}
}

配置此拦截器:

    <interceptors>
<interceptor name="privilegeInterceptor" class="myIntercept">
</interceptor>
<interceptor-stack name="privilegeStack">
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="privilegeInterceptor"></interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="privilegeStack"></default-interceptor-ref>

这样当用户进行登录的时候就从数据库中查询用户具有的权限,然后把其保存在session中。然后用户要访问某个方法时会经过我们拦截器的。在拦截器中获取该注解信息,然后与保存在session中的用户的所有权限进行判断,当拥有此权限则容许方法,否则拒绝。

2.使用springAOP来判断:

Aop的概念:
切面
事务、日志、安全性的框架,权限等就是切面 (就是在执行具体的方法之前要操作的那个类)
通知
切面中的方法就是通知
切入点
只有符合切入点的条件,才能让通知和目标方法结合在一起
织入
形成代理对象方法体的过程.
(注意SpringAOP的实现默认是面前接口的代理,而不是CGLIB。如果一定要CGLIB导入即可,SpringAOP两种都支持)

我们定义一个切面来代替上面的拦截器,其余的不变:

@Component(value="aspect")
public class Aspect {

//定义一个环绕通知
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
Collection<Privilege> privileges = (List<Privilege>) ServletActionContext.getRequest().getSession()
.getAttribute("functions");
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
if(user!=null&&privileges!=null){
//获取请求的类和方法
Class actionClass = joinPoint.getTarget().getClass();
String methodName = joinPoint.getSignature().getName();
//获取访问目标的权限名
String privilegeName = PrivilegeInfoAnnotationParse.parse(actionClass, methodName);
System.out.println(privilegeName);
//是否通过访问
boolean flag = false;
if(privilegeName==null||privilegeName.equals("")){
flag=true; //没有权限,获取权限为""说明任何人都可以访问
}else if(user.getUsername().equals("admin")){
flag=true;
}else {
for(Privilege p : privileges){
if(p.getName().equals(privilegeName)){//用户能够访问的权限中包含有当前方法的访问权限
flag = true;
break;
}
}

if(flag){
joinPoint.proceed();
}else{
//权限不足
}
}
else{
joinPoint.proceed();
}
}
}

Spring配置文件:

    <!--配置AOP-->
<aop:config>
<!--配置AOP切面表达式-->
<aop:pointcut id="pointcut" expression="execution(* action.*.*(..))"></aop:pointcut>
<!--配置通知-->
<aop:aspect ref="aspect">
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>

如上则是开发中经常使用的权限管理。一些开源社区也有一些控制权限的框架,如SpringSecure和Shrio。感兴趣的同学可以自己研究一下。