起因
项目是由组员从其他老项目中拷贝而来,基于 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 "";
}
}
复制代码
参考: