欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring AOP实现功能权限校验功能的示例代码

程序员文章站 2023-12-04 11:46:16
实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用aop抛异常。 首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用aop切入,而是用...

实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用aop抛异常。

首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用aop切入,而是用拦截器拦截,因为aop一般切入的是service层方法,而拦截器是拦截控制器层的请求,它本身也是一个处理器,可以直接中断请求的传递并返回视图,而aop则不可以。

1.使用拦截器实现未登录时跳转到登录界面的功能

1.1 拦截器securityinterceptor

package com.jykj.demo.filter;
import java.io.printwriter;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import javax.servlet.http.httpsession;
import org.springframework.web.servlet.handlerinterceptor;
import org.springframework.web.servlet.modelandview;
import com.alibaba.fastjson.json;
import com.jykj.demo.util.helper;
import com.jykj.demo.util.result;

public class securityinterceptor implements handlerinterceptor{
 @override
 public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler)
   throws exception {
  system.out.println("securityinterceptor:"+request.getcontextpath()+","+request.getrequesturi()+","+request.getmethod());
  httpsession session = request.getsession();
  if (session.getattribute(helper.session_user) == null) {
   system.out.println("authorizationexception:未登录!"+request.getmethod());
   if("post".equalsignorecase(request.getmethod())){
    response.setcontenttype("text/html; charset=utf-8"); 
    printwriter out = response.getwriter(); 
    out.write(json.tojsonstring(new result(false,"未登录!")));
    out.flush();
    out.close();
   }else{
    response.sendredirect(request.getcontextpath()+"/login"); 
   }
   return false;
  } else {
   return true;
  }
 }

 @override
 public void posthandle(httpservletrequest request, httpservletresponse response, object handler,
   modelandview modelandview) throws exception {
  // todo auto-generated method stub
 }

 @override
 public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex)
   throws exception {
  // todo auto-generated method stub
 }
}

1.2.spring-mvc.xml(拦截器配置部分)

<!-- handles http get requests for /resources/** by efficiently serving up static resources in the ${webapproot}/resources directory -->
 <mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:interceptors>
  <mvc:interceptor>
   <mvc:mapping path="/*"/> <!-- 拦截/ /test /login 等等单层结构的请求 --> 
   <mvc:mapping path="/**/*.aspx"/><!-- 拦截后缀为.aspx的请求 -->
   <mvc:mapping path="/**/*.do"/><!-- 拦截后缀为 .do的请求 -->
   <mvc:exclude-mapping path="/login"/>
   <mvc:exclude-mapping path="/signin"/>
   <mvc:exclude-mapping path="/register"/>
   <bean class="com.jykj.demo.filter.securityinterceptor">
   </bean>
  </mvc:interceptor>
 </mvc:interceptors>

这里需要特别说明:拦截器拦截的路径最好是带有后缀名的,否则一些静态的资源文件不好控制,也就是说请求最好有一个统一的格式如 .do 等等,这样匹配与过滤速度会非常快。如果不这样,例如 用 /** 来拦截所有的请求,则页面渲染速度会非常慢,因为资源文件也被拦截了。

2.使用aop实现功能权限校验

对于功能权限校验也可以类似地用拦截器来实现,只不过会拦截所有的请求,对不需要权限校验的请求没有很好的过滤功能,所以采用aop指定拦截需要校验的方法的方式来实现之。

2.1 切面类 permissionaspect

package com.jykj.demo.filter;
import java.io.ioexception;
import java.lang.reflect.method;
import org.aspectj.lang.joinpoint;
import org.aspectj.lang.reflect.methodsignature;
import org.springframework.beans.factory.annotation.autowired;
import com.jykj.demo.annotation.validatepermission;
import com.jykj.demo.exception.accessdeniedexception;
import com.jykj.demo.service.sysuserrolepermservice;
/**
 * 事件日志 切面,凡是带有 @validatepermission 以及@responsebody注解 控制器 都要进行 功能权限检查,
 * 若无权限,则抛出accessdeniedexception 异常,该异常将请求转发至一个控制器,然后将异常结果返回
 * @author administrator
 *
 */
public class permissionaspect {
 @autowired
 sysuserrolepermservice sysuserrolepermservice;
 public void dobefore(joinpoint jp) throws ioexception{
  system.out.println(
    "log permissionaspect before method: " + jp.gettarget().getclass().getname() + "." + jp.getsignature().getname());
  method sorucemethod = getsourcemethod(jp);
  if(sorucemethod!=null){
   validatepermission oper = sorucemethod.getannotation(validatepermission.class);
   if (oper != null) {
    int fidx = oper.idx();
    object[] args = jp.getargs();
    if (fidx>= 0 &&fidx<args.length){
     int functionid = (integer) args[fidx];
     string rs = sysuserrolepermservice.permissionvalidate(functionid);
     system.out.println("permissionvalidate:"+rs);
     if(rs.trim().isempty()){
      return ;//正常
     }
    }
   }
  }
  throw new accessdeniedexception("您无权操作!");
 }
 private method getsourcemethod(joinpoint jp){
  method proxymethod = ((methodsignature) jp.getsignature()).getmethod();
  try {
   return jp.gettarget().getclass().getmethod(proxymethod.getname(), proxymethod.getparametertypes());
  } catch (nosuchmethodexception e) {
   e.printstacktrace();
  } catch (securityexception e) {
   e.printstacktrace();
  }
  return null;
 }
}

2.2自定义注解validatepermission

package com.jykj.demo.annotation;
import java.lang.annotation.documented;
import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;
/**
 * @descrption该注解是标签型注解,被此注解标注的方法需要进行权限校验
 */
@target(value = elementtype.method)
@retention(value = retentionpolicy.runtime)
@documented
public @interface validatepermission {
 /**
  * @description功能id的参数索引位置 默认为0,表示功能id在第一个参数的位置上,-1则表示未提供,无法进行校验
  */
 int idx() default 0;
}

说明: aop切入的是方法,不是某个控制器请求,所以不能直接返回视图来中断该方法的请求,但可以通过抛异常的方式达到中断方法执行的目的,所以在before通知中,如果通过验证直接return返回继续执行连接点方法,否则抛出一个自定义异常accessdeniedexception来中断连接点方法的执行。该异常的捕获可以通过系统的异常处理器(可以看做控制器)来捕获并跳转到一个视图或者一个请求。这样就达到拦截请求的目的。所以需要配置异常处理器。

2.3 spring-mvc.xml(异常处理器配置,以及aop配置)

<bean class="org.springframework.web.servlet.handler.simplemappingexceptionresolver">
  <!-- <property name="defaulterrorview" value="rediret:/error"></property> -->
  <property name="exceptionmappings">
   <props>
    <!--<prop key="com.jykj.demo.exception.authorizationexception">redirect:/login</prop>-->
    <prop key="com.jykj.demo.exception.accessdeniedexception">forward:/accessdenied</prop>
   </props>
  </property>
 </bean>
<bean id="aspectpermission" class="com.jykj.demo.filter.permissionaspect" />
 <!-- 对带有@validatepermission和responsebody注解的controller包及其子包所有方法执行功能权限校验 --> 
 <aop:config proxy-target-class="true"> 
  <aop:aspect ref="aspectpermission"> 
   <aop:pointcut id="pc" 
    expression="@annotation(com.jykj.demo.annotation.validatepermission) 
    and @annotation(org.springframework.web.bind.annotation.responsebody) 
    and execution(* com.jykj.demo.controller..*.*(..)) " /> 
   <aop:before pointcut-ref="pc" method="dobefore"/> 
  </aop:aspect> 
 </aop:config>

2.4 注解需要进行功能校验的控制器请求

@requestmapping(value = "/moduleaccess.do", method = requestmethod.post, produces="text/html;charset=utf-8")
 @responsebody
 @validatepermission
 public string moduleaccess(int fid,string action,frmmodule module) {
  system.out.println("fid:"+fid+",action:"+action);
  int rs = -1;
  try{
   if(helper.f_action_create.equals(action)){
    rs = moduleservice.access(module,helper.db_action_insert);
    //module.setmoduleid(rs);
    module = moduleservice.selectbyprimarykey(rs);
   }else if(helper.f_action_edit.equals(action)){
    rs = moduleservice.access(module,helper.db_action_update);
    module = moduleservice.selectbyprimarykey(module.getmoduleid());
   }else if(helper.f_action_remove.equals(action)){
    rs = moduleservice.access(module,helper.db_action_delete);
   }else{
    return json.tojsonstring(new result(false,"请求参数错误:action"));
   }
  }catch(exception e){
   e.printstacktrace();
   return json.tojsonstring(new result(false,"操作失败,出现异常,请联系管理员!"));
  }
  if(rs<0){
   return json.tojsonstring(new result(false,"操作失败,请联系管理员!"));
  }
  return json.tojsonstring(new result(true,module));
 }

2.5 异常处理器将请求转发到的控制器请求 forward:/accessdenied

@requestmapping(value = "/accessdenied",produces = "text/html;charset=utf-8")
@responsebody
public string accessdenied(){
 return json.tojsonstring(new result(false,"您没有权限对此进行操作!"));
}

2.6 请求校验不通过时 由上述的控制器返回 结果本身

如下所示:

{"info":"您没有权限对此进行操作!","success":false}

2.7 功能校验service 示例

/**
  * 校验当前用户在某个模块的某个功能的权限
  * @param functionid
  * @return 空字符串表示 有权限 ,否则是错误信息
  * @throws exception 
  */
 public string permissionvalidate(int functionid){
  object o = request.getsession().getattribute(helper.session_user);
  //if(o==null) throw new authorizationexception(); 
  sysuser loginuser= (sysuser)o;
  if(loginuser.getuserid() == 1) return "";
  try{
   return mapper.permissionvalidate(loginuser.getuserid(),functionid);
  }catch(exception ex){
   ex.printstacktrace();
   return "数据库操作出现异常!";
  }
 }

说明: 这里仅仅是对带有@validatepermission和@responsebody注解的controller包及其子包所有方法进行切入,这样肯定是不够通用的,应该是对带有@validatepermission的方法进行切入,在切面类中通过判断该方法是否有@responsebody注解来抛出不一样的异常,若带有@responsebody注解则抛出上述的异常返回json字符串,
否则,应该抛出另一个自定义异常然后将请求重定向到一个合法的视图如error.jsp .

通过客户端发送 /moduleaccess.do 请求,该请求对应的方法同时具有@validatepermission和@responsebody,并且有功能id参数fid,这样aop可以切入该方法,执行dobefore通知,通过功能参数fid,对它结合用户id进行权限校验,若校验通过直接返回,程序继续执行,否则抛出自定义异常accessdeniedexception,该异常由系统捕获(需要配置异常处理器)并发出请求 forward:/accessdenied ,然后对应的控制器 /accessdenied 处理该请求返回一个包含校验失败信息的json给客户端。这样发送 /moduleaccess.do 请求,如果校验失败,转发到了/accessdenied请求,否则正常执行。绕了这么一个大圈子才实现它。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。