springboot通过自定义注解实现AOP角色权限校验

这里有几个地方需要注意:1.自定义注解 2.aop,拦截器,过滤器的区别?3.如何获取方法上的自定义注解的属性值?
1.在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法。
元注解:
  元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:1.@Target,2.@Retention,3.@Documented,4.@Inherited,具体的可以参考博文:深入JAVA注解(Annotation):自定义注解

2.首先看下过滤器和拦截器的区别。如下图。
从一个请求到返回数据的整个过程看,首先是请求进入Servlet,在Servlet中存在过滤器。对url进行拦截。后经过分发器后,会进入到spring容器,在请求尚未到达具体的controller时,会先经过拦截器的preHandle方法。方法通过后,进入业务层处理,返回实体,在进入postHandle方法。经过JSP后会进入afterCompletion方法,返回页面ModleAndView.
springboot通过自定义注解实现AOP角色权限校验_第1张图片
过滤器
过滤器是第一道拦截配置文件配置好的拦截条件拦截URL

Spring中自定义过滤器(Filter)一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤器任务完成。过滤器并没有定义业务逻辑执行前、后等,仅仅是请求到达就执行。

特别注意:过滤器方法的入参有request,response,FilterChain,其中FilterChain是过滤器链,使用比较简单,而request,response则关联到请求流程,因此可以对请求参数做过滤和修改,同时FilterChain过滤链执行完,并且完成业务流程后,会返回到过滤器,此时也可以对请求的返回数据做处理。

拦截器
拦截器是第二道拦截URL,根据代码中的表达式拦截符合条件的url

拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。分别表示如下

public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)表示被拦截的URL对应的方法执行前的自定义处理

public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)表示此时还未将modelAndView进行渲染,被拦截的URL对应的方法执行后的自定义处理,。

public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)表示此时modelAndView已被渲染,执行拦截器的自定义处理。

AOP(面向切面)
面向切面拦截的是类的元数据(包、类、方法名、参数等),拦截是在controller层的类,接口,或者service层的类,方法的执行前后进行拦截。

相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。具体类型参照其他博客。

三者使用场景
三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能.

具体的aop实现:
首先pom中引入依赖

        
            org.springframework.boot
            spring-boot-starter-aop
        

1.自定义注解

package com.zhanglf.aop.annotation;

import java.lang.annotation.*;

/**
 * 使用注解统一校验角色权限
 * @author zhanglf
 * @date 2019-04-29
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionCheck {
    //自定义角色值,如果是多个角色,用逗号分割。
    public String role() default "";
}

2.aop绑定自定义注解。并在aop中实现处理逻辑

package com.jd.mee.catalog.aop;


import com.zhanglf.aop.annotation.PermissionCheck;
import com.zhanglf.common.result.CodeMsg;
import com.zhanglf.common.util.JwtUtil;
import com.zhanglf.entity.UserInfo;
import com.zhanglf.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * 角色权限校验-AOP
 * @author  zhanglf
 * @date 2019-04-29
 */
@Aspect
@Component
@Slf4j
public class PermissionCheckAspect {

    //切入点表达式决定了用注解方式的方法切还是针对某个路径下的所有类和方法进行切,方法必须是返回void类型
    @Pointcut("@annotation(com.zhanglf.aop.annotation.PermissionCheck)")
    private void permissionCheckCut(){};

    //定义了切面的处理逻辑。即方法上加了@PermissionCheck
    @Around("permissionCheckCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("==========哈哈哈,进入AOP============================");
        //1.记录日志信息
        Signature signature = pjp.getSignature();
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = signature.getName();
        log.info("className:{},methodName:{}",className,methodName);

        //2.角色权限校验
        MethodSignature methodSignature = (MethodSignature)signature;
        Method targetMethod = methodSignature.getMethod();
        if(targetMethod.isAnnotationPresent(PermissionCheck.class)){
            //获取方法上注解中表明的权限
            PermissionCheck permission = (PermissionCheck)targetMethod.getAnnotation(PermissionCheck.class);
            String role = permission.role();
            log.info("当前接口请求的用户角色role:{}",role);
            if(StringUtils.isNotEmpty(role)){
                String[] roles = role.split(",");//接口允许的角色
                List list = Arrays.asList(roles);
                //如果该接口只允许老师角色访问。则要获取当前用户是不是老师角色。
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String authorization = request.getHeader("Authorization");
                UserInfo userInfo = JwtUtil.parseAccessToken(authorization);
                String userRole = userInfo.getUserRole();//用户的角色
                if(list.contains(userRole)){
                    log.info("AOP权限角色校验通过,进入业务层处理!");
                    //3.执行业务逻辑,放行
                    return pjp.proceed();
                }else{
                    //如果没有权限,抛出异常,由Spring框架捕获,跳转到错误页面
                    throw new BizException(CodeMsg.ROLE_HAVE_NO_PERMISSION);
                }
            }
        }
        throw new BizException(CodeMsg.NO_ROLE_CONFIGER);
    }

}

3.aop的使用:用在接口层校验登陆的角色是否有权限使用该接口
springboot通过自定义注解实现AOP角色权限校验_第2张图片
参考博文:

  1. Spring拦截器和过滤器
  2. Spring 过滤器 拦截器 AOP区别

你可能感兴趣的:(接口设计与安全)