使用 intercepter + 自定义注解实现简单基于 url 的权限控制

起因

项目是由组员从其他老项目中拷贝而来,基于 SpringMVC 和其中的 Intercepter 技术实现了一套简单的权限控制。通过判断请求后端的 url 后缀来控制,后缀为 .do 时进入权限判断,后缀为 .pub 时,不对其进行权限拦截。

这种做法,既和前端代码进行了耦合,url 设计上也不灵活,每个开出去的地址都必须为 .do 或者是 .pub 中选择。

于是就对权限控制进行了一次小的重构(没有引入权限框架)。希望达到的效果是:

  • 能够灵活控制每个 controller 的方法,对后缀没有限制
  • 当需要权限控制时,仅需要在后端方法上加注解
  • 该注解可以作用于类和方法上

重构时,发现在 SpringMVC 中存在多种拦截器,Filter、Aspect、ControllerAdvice、Intercepter,为什么当初就选用了 Intercepter 技术,而不是用 Filter、Aspect 等

带着问题查了资料,发现几种拦截器有如下区别:

web 项目中各种拦截器

Aspect、ControllerAdvice、Intercepter 都是 SpringMVC 框架中的概念。而 Filter 是 Servlet 规范中的,与具体实现框架无关。

  • Filter 的控制的粒度最大,在请求链在最外层,一般用于做日志统一输出、请求编码过滤等操作。

过滤器依赖于 servlet 容器。在实现上,基于函数回调,可以对几乎所有请求进行过滤,一个过滤器实例只能在容器初始化时调用一次。

  • Intercepter 在 Filter 之后执行,是 Spring 基于 Aop 技术提供的 Controller 层拦截器。一般用于登录校验、权限校验等操作。
  • Aspect 其实就是 aop 切面。

各个组件执行顺序图

且使用 intercepeter 能拿到 spring 容器管理的类,而 filter 不行

Spring 中,web 应用启动的顺序是:listener->filter->servlet,先初始化 listener,然后是 filter 的初始化,再接着才到 DispatcherServlet 的初始化。因此,当需要在 filter 里注入一个注解(Autowired)的 bean 时,就会注入失败,因为 filter 初始化时,注解的 bean 还没初始化。

实现

定义权限注解

@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Documented
public @interface Permission {
    // 是否激活,默认为 true,激活状态
    // 为 false 时,不进行权限校验
    boolean active() default true;
}
复制代码

定义并注册权限 Intercepter

public class PermissionInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request 
    HttpServletResponse response, Object handler) {
        return true;
    }
}
复制代码

注册

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PermissionInterceptor()).addPathPatterns("/**/*");
    }
}
复制代码

覆写 PermissionInterceptor#preHandle 方法如下:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    // 说明是 GET 请求是请求静态文件
    if (!(handler instanceof HandlerMethod)) {
        return true;
    }

    // 判断是否有权限
    HandlerMethod method = (HandlerMethod) handler;
    // 先判断 controller 类上是不是有注解 Permission
    // 如果有注解,则每个方法都需要进行权限校验
    Object controllerBean = method.getBean();

    Permission permissionOnClass = controllerBean.getClass().getAnnotation(Permission.class);
    Permission permissionOnMethod = method.getMethodAnnotation(Permission.class);

    if (permissionOnClass != null) {
        if (permissionOnMethod != null && !permissionOnMethod.active()) {
            // 当方法上 Permission 注解存在,且状态为非激活,则不校验
            return true;
        }
        else {
            return this.doAuth(request, response);
        }
    }
    else {
        // 类上无注解,但方法上有注解,且为激活状态
        if (permissionOnMethod != null && permissionOnMethod.active()) {
            return this.doAuth(request, response);
        }
        return true;
    }

    // doAuth 方法省略,可替换成自定义权限校验操作
}
复制代码

使用范例:

@RestController
public class TestController {

    @GetMapping("/detail")
    public String get() {
        return "";
    }

    // 当需要权限控制时,才加注解
    @Permission
    @PostMapping("/add")
    public String put() {
        return "";
    }
}
复制代码

参考:

猜你喜欢

转载自juejin.im/post/5e1d2b63f265da3e21709e29
今日推荐