springBoot|自定义拦截器实现权限校验

背景
【springBoot|自定义拦截器实现权限校验】前几天接收到一个业务需求,简要描述一下,当某个企业账户冻结之后,企业在登陆后台管理系统,不能进行某些操作,如导入员工信息、发放员工福利、发票信息申请等,但是系统中的查看功能如账单查询、员工查询都可以正常使用。
很显然,从全局去处理账户的状态判断是不可取的,因为在账户冻结之后,并不是所有的动作都是被禁止的,这里就可以使用过滤器对指定的方法进行筛选校验
既定方案
使用自定义的注解,标注需要进行账户状态校验的请求方法,使用拦截器拦截这些接口,在拦截器中执行账户状态的判断,如果账户状态正产则放行,如果状态异常则抛出自定义的异常,并且使用自定义的异常处理类进行捕获处理。基本思路就是这样,下面我们来进行方案的实现。
方案实现

  • 第一步:自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FrozenAcc { boolean permission() default false; }

方法级别以及运行时状态即可。
  • 第二步:自定义拦截器
@Slf4j public class FrozenFilter extends HandlerInterceptorAdapter {@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //拦截自定义的注解 HandlerMethod handlerMethod = (HandlerMethod) handler; FrozenAcc annotation = handlerMethod.getMethod().getAnnotation(FrozenAcc.class); if (annotation == null) { return true; } //获取如果处于冻结状态,则抛出一个异常信息 String accStatus = UserUtil.getAccStatus(); if ("1".equals(accStatus)) { return true; } throw new FrozenException(444, "您好,您的账户因逾期未还款已被冻结,无法进行当前操作,请尽快还款后申请解冻!"); } }

  • 第三步:注册拦截器类,使拦截器生效
@Configuration public class CheckConfig implements WebMvcConfigurer {@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FrozenFilter()).addPathPatterns("/employee/enterpriseEmployee/**"); registry.addInterceptor(new FrozenFilter()).addPathPatterns("/enterprisepoints/enterprisePointsBatch/**"); registry.addInterceptor(new FrozenFilter()).addPathPatterns("/invoice/**"); } }

可注册多个拦截器,并指定拦截规则,这里我对员工管理、企业积分管理、以及发票信息管理做了拦截
  • 第四步:控制层中,在需要进行账户信息校验的接口上添加注解
@FrozenAcc @RequiresPermissions("tb:enterpriseEmployee:add") @PostMapping(value = "https://www.it610.com/article/add") public R add(EnterpriseEmployee enterpriseEmployee) { return enterpriseEmployeeService.saveEmployee(enterpriseEmployee); }

如上,在员工管理中,新增员工信息的方法上面添加FrozenAcc注解
  • 第五步:自定义异常以及异常处理
自定义异常类,用于拦截器中校验账户状态时,不符合时抛出
@Data @EqualsAndHashCode(callSuper = false) public class FrozenException extends RuntimeException {private int code; private String message; public FrozenException(int code, String message) { this.message = message; this.code = code; } }

异常处理类,用于捕获处理自定义的异常,封装响应参数给前端
@ExceptionHandler(FrozenException.class) public R handleFrozenException(FrozenException e) { return R.fail(e.getCode(), e.getMessage()); }

总结
以上就可以实现对指定接口实现账户状态的校验,能够符合业务的需要,但是还是存在改进之处的。
在请求指定模块时,凡是加上注解的方法都需要去校验账户的状态,目前是采取直查数据库的方式,在日志中能发现多次查询的sql语句,一个是增加系统的IO开销,还有就是重复的查询逻辑,之前有考虑过在用户登录之后,就将企业的账户状态放到全局的会话缓存当中,但是有个弊端就是,如果用户在登陆成功之后,账户状态过期冻结,那么这个 用户还是可以进行操作的,并没有达到实时的状态过滤。
可以引入缓存机制,提高性能,但是必须还得配上缓存的更新策略。可优化的方案如下:
  • 登录时,将企业的账户状态放到redis缓存中,并在企业账户状态变更的逻辑中(具体的操作或者定时任务等),添加账户状态的更新逻辑,将最新的状态更新至redis当中。而在拦截器中,就可以直接使用redis中的值来判断企业的账户状态,使用缓存提升查询效率避免直查数据库。

    推荐阅读