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

作者:後雪寒 时间:2021-10-18 21:32:29 

实现功能权限校验的功能有多种方法,其一使用 * 拦截请求,其二是使用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请求,否则正常执行。绕了这么一个大圈子才实现它。

来源:http://blog.csdn.net/houxuehan/article/details/51745175

标签:Spring,AOP,权限
0
投稿

猜你喜欢

  • C#操作windows系统进程的方法

    2023-06-09 05:11:45
  • 秒懂Kotlin之Java工程师快速掌握Kotlin的技巧

    2023-07-09 21:25:19
  • Java字符串驼峰与下换线格式转换如何实现

    2022-02-18 09:56:14
  • android打开本地图像的方法

    2022-10-26 08:01:42
  • android中SQLite使用及特点

    2023-07-24 23:33:28
  • java poi导出图片到excel示例代码

    2023-10-30 00:13:17
  • 详解Android获取系统内核版本的方法与实现代码

    2023-11-25 14:51:59
  • java String 转成Double二维数组的方法

    2023-04-28 12:11:02
  • 一文看懂JAVA设计模式之工厂模式

    2023-11-27 02:30:54
  • SpringBoot2.x过后static下的静态资源无法访问的问题

    2023-07-07 00:21:09
  • Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    2023-06-04 04:54:22
  • SpringBoot2整合activiti6环境搭建过程解析

    2023-11-09 02:27:03
  • Java判断字符串是否为IP地址的方法

    2023-07-06 15:02:58
  • 详解java基于MyBatis使用示例

    2023-11-25 09:01:45
  • Jenkins一键打包部署SpringBoot应用

    2022-08-03 16:37:18
  • 使用@PathVariable接收两个参数

    2022-12-08 14:21:04
  • springboot+thymeleaf+druid+mybatis 多模块实现用户登录功能

    2022-09-17 21:36:41
  • Springboot2.0处理自定义异常并返回json

    2021-06-01 03:42:24
  • opencv利用鼠标滑动画出多彩的形状

    2023-11-03 05:20:57
  • C# 中将数值型数据转换为字节数组的方法

    2023-09-29 05:46:17
  • asp之家 软件编程 m.aspxhome.com