在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个)。
拦截器的底层原理是AOP

Interceptor 作用

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  • 权限检查:如登录检测,进入处理器检测是否登录;
  • 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)

HandlerInterceptor接口方法的作用:

  • preHandle:处理器执行之前执行,如果返回 false 将跳过处理器、拦截器 postHandle 方法、视图渲染等,直接执行拦截器 afterCompletion 方法。
  • postHandle:处理器执行后,视图渲染前执行,如果处理器抛出异常,将跳过该方法直接执行拦截器 afterCompletion 方法。
  • afterCompletion:视图渲染后执行,不管处理器是否抛出异常,该方法都将执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @Component
    @Slf4j
    public class JwtTokenUserInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
    * 校验jwt
    *
    * @param request
    * @param response
    * @param handler
    * @return
    * @throws Exception
    */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //判断当前拦截到的是Controller的方法还是其他资源
    if (!(handler instanceof HandlerMethod)) {
    //当前拦截到的不是动态方法,直接放行
    return true;
    }

    //1、从请求头中获取令牌
    String token = request.getHeader(jwtProperties.getUserTokenName());
    //2、校验令牌
    try {
    log.info("jwt校验user:{}", token);
    Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
    Long empId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
    log.info("当前员工id:", empId);
    BaseContext.setCurrentId(empId);
    //3、通过,放行
    return true;
    } catch (Exception ex) {
    //4、不通过,响应401状态码
    response.setStatus(401);
    return false;
    }


    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //请求结束后清空线程
    BaseContext.removeCurrentId();
    }
    }

配置类配置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
// .addPathPatterns("/user/shoppingCart/add")
// .excludePathPatterns("/user/user/**")
.excludePathPatterns("/admin/employee/login");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login");
}
}

AOP实现拦截器

拦截器的底层其实就是AOP,下面是一个AOP配合注解完成拦截器的功能

定义切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Component("operationAspect")
@Aspect
public class GlobalOperationAspect {


@Resource
private RedisUtils redisUtils;

private static Logger logger = LoggerFactory.getLogger(GlobalOperationAspect.class);


@Before("@annotation(com.easychat.annotation.GlobalInterceptor)")
public void interceptorDo(JoinPoint point) {
try {
// 获取切入点的方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
// 获取注解并判断注解类型
GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
if (null == interceptor) {
return;
}
/**
* 校验登录
*/
if (interceptor.checkLogin() || interceptor.checkAdmin()) {
//调用自定义拦截方法
checkLogin(interceptor.checkAdmin());
}
} catch (BusinessException e) {
logger.error("全局拦截器异常", e);
throw e;
} catch (Exception e) {
logger.error("全局拦截器异常", e);
throw new BusinessException(ResponseCodeEnum.CODE_500);
} catch (Throwable e) {
logger.error("全局拦截器异常", e);
throw new BusinessException(ResponseCodeEnum.CODE_500);
}
}

//校验登录
private void checkLogin(Boolean checkAdmin) {
//获取Springboot 提供的HttpServletRequest对象request, 这里我觉得其实可以实现 HandlerInterceptor接口
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//下面是获取token,判断的逻辑根据具体需求具体分析
String token = request.getHeader("token");
TokenUserInfoDto tokenUserInfoDto = (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_WS_TOKEN + token);
if (tokenUserInfoDto == null) {
throw new BusinessException(ResponseCodeEnum.CODE_901);
}
if (checkAdmin && !tokenUserInfoDto.getAdmin()) {
throw new BusinessException(ResponseCodeEnum.CODE_404);
}
}
}

注解代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface GlobalInterceptor {

/**
* 校验登录
*
* @return
*/
boolean checkLogin() default true;

/**
* 校验管理员
*
* @return
*/
boolean checkAdmin() default false;
}