天天看点

SpringBoot + Interceptor 实现登录权限、异常处理统一解决!

作者:程序猿怪咖
SpringBoot + Interceptor 实现登录权限、异常处理统一解决!

1 简介

Spring Boot中统一统一处理用户登录权限、异常、返回数据格式实际上都是用到了AOP。

  • 异常处理使用注解: @RestControllerAdvice + @ExceptionHandler
  • 用户登录权限的校验实现接口: HandlerInterceptor + WebMvcConfigurer
  • 数据格式返回: @ControllerAdvice 注解+ 实现接口 @ResponseBodyAdvice

2 用户登录权限处理方案

其实现在市面上最常用的是SPring拦截器处理用户登录异常

拦截器实现原理:

SpringBoot + Interceptor 实现登录权限、异常处理统一解决!
  • 方案一:用户登录权限校验

在每个方法中获取 Session 和 Session 中的用户信息,如果存在用户,那么就认为登录成功了,否则就登录失败了。

该方案时最早的处理方式,每个方法中都有相同的用户登录验证权限.
缺点:
  1、每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断
  2、添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成功和维护成功
  3、这些用户登录验证的方法和现在要实现的业务几乎没有任何关联,但还是要在每个方法中都要写一遍,所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证是非常好的解决办法。           
  • 方案二:用户登录权限校验

提供统一的方法,在每个需要验证的方法中调用统一的用户登录身份校验方法来判断。

这种方案实际上就是把方案一的方式进行代码抽取为公共方法,哪个方法需要的时候就直接调用。
同样的每个方法中也要去处理一下,比较繁琐。           
  • 方案三:Spring AOP统一处理用户登录权限

下面是简单的AOP处理用户登录权限校验实现案例

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
 // 定义切点方法 Controller 包下、子孙包下所有类的所有方法
 @Pointcut("execution(* com.test.user.controller..*.*(..))")
 public void pointcut(){}
 
 // 前置通知
 @Before("pointcut()")
 public void doBefore() {}
 
 // 环绕通知
 @Around("pointcut()")
 public Object doAround(ProceedingJoinPoint joinPoint) {
 Object obj = null;
 System.out.println("Around 方法开始执行");
 try {
 obj = joinPoint.proceed();
 } catch (Throwable e) {
 e.printStackTrace();
 }
 System.out.println("Around 方法结束执行");
 return obj;
 }
}           
缺点:
  1、没有办法得到 HttpSession 和 Request 对象
  2、我们要对一部分方法进行拦截,而另一部分方法不拦截,
        比如注册方法和登录方法是不拦截的,也就是实际的拦截规则很复杂,
        使用简单的 aspectJ 表达式无法满足拦截的需求           
  • 方案四:Spring拦截器处理用户登录权限校验

针对上面代码 Spring AOP 的问题,Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现有两步:

1.自定义拦截器,实现 Spring 中的 HandlerInterceptor 接口中的 preHandle方法

2.将自定义拦截器加入到框架的配置中,并且设置拦截规则

  • 给当前的类添加 @Configuration 注解
  • 实现 WebMvcConfigurer 接口
  • 重写 addInterceptors 方法

自定义拦截器实现

/**
* @Description: 自定义用户登录的拦截器
* @Date 2023/4/22 13:06
*/
@Component
public class LoginIntercept implements HandlerInterceptor {

 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
 // 1.得到 HttpSession 对象
 HttpSession session = request.getSession(false);
 if (session != null && session.getAttribute("userinfo") != null) {
 // 表示已经登录
 return true;
 }
 // 执行到此代码表示未登录,未登录就跳转到登录页面
 response.sendRedirect("/login.html");
 return false;
 }
}           

注册自定义拦截器,并设置拦截器规则

/**
* @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
* @Date 2023/4/22 13:13
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {

 @Resource
 private LoginIntercept loginIntercept;

 @Override
 public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
 registry.addInterceptor(loginIntercept).
 addPathPatterns("/**"). // 拦截所有 url
 excludePathPatterns("/user/login"). //不拦截登录注册接口
 excludePathPatterns("/user/reg").
 excludePathPatterns("/login.html").
 excludePathPatterns("/reg.html").
 excludePathPatterns("/**/*.js").
 excludePathPatterns("/**/*.css").
 excludePathPatterns("/**/*.png").
 excludePathPatterns("/**/*.jpg");
 }
}           
  • addPathPatterns:表示需要拦截的 URL,**表示拦截所有⽅法
  • excludePathPatterns:表示需要排除的 URL
拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。           

3 统一异常处理

@ControllerAdvice 注解表示控制器通知类

@ExceptionHandler(xxx.class)注解表示异常处理器,添加异常返回的业务代码

自定义MyExceptionAdvice:

@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
//@ControllerAdvice用这个注解也一样的
public class MyExceptionAdvice {
 @ExceptionHandler(ArithmeticException.class)
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
 HashMap<String, Object> result = new HashMap<>();
 result.put("state",-1);
 result.put("data",null);
 result.put("msg" , "算出异常:"+ e.getMessage());
 return result;
 }
}           

空指针异常处理器:

@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) {
 HashMap<String, Object> result = new HashMap<>();
 result.put("state",-1);
 result.put("data",null);
 result.put("msg" , "空指针异常异常:"+ e.getMessage());
 return result;
}           

4 统一处理返回数据格式

给当前类添加 @ControllerAdvice

实现 ResponseBodyAdvice 重写其方法

  • supports 方法,此方法表示内容是否需要重写(通过此⽅法可以选择性部分控制器和方法进行重写),如果要重写返回 true
  • beforeBodyWrite 方法,方法返回之前调用此方法
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {

 // 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
 // 返回 false 表示对结果不进行任何处理,直接返回
 @Override
 public boolean supports(MethodParameter returnType, Class converterType) {
 return true;
 }

 // 方法返回之前调用此方法
 @Override
 public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
 HashMap<String,Object> result = new HashMap<>();
 result.put("state",1);
 result.put("data",body);
 result.put("msg","");
 return result;
 }
}