SpringBoot_拦截器


SpringBoot_拦截器

  • preHandle 预处理回调方法,实现处理器的预处理。返回值:true表示继续流程。false表示流程中断不会继续调用其他的拦截器或处理器,此时我们需要通过 response 来产生响应。
  • postHandle 后处理回调方法,实现处理器的后处理。
  • afterCompletion 整个请求处理完毕回调方法。

定义拦截器,实现接口 HandlerInterceptor

@Component
@Slf4j
public class ApiInterceptor implements HandlerInterceptor {

    @Resource
    private ITUserService itUserService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String TOKEN = "X-Access-Token";

    private static final Long VALID_TIME = 24 * 60 * 60L;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader(TOKEN);
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter(TOKEN);
        }
        if (StringUtils.isEmpty(token)) {
            Cookie[] cookies = request.getCookies();
            if (cookies == null) {
                throw new JeecgBoot401Exception("请求token为空");
            }
            for (Cookie cookie : cookies) {
                if (Objects.equals(cookie.getName(), TOKEN)) {
                    token = cookie.getValue();
                }
            }
        }
        if (StringUtils.isBlank(token)) {
            throw new JeecgBoot401Exception("请求token为空");
        }
        String tokenKey = String.format(PRODUCT_USER_TOKEN, token);
        String userInfo = stringRedisTemplate.opsForValue().get(tokenKey);
        if (StringUtils.isNotEmpty(userInfo)) {
            AuthInfo authInfo = JSON.parseObject(userInfo, new TypeReference<AuthInfo>() {
            });
            CurrentUserUtil.set(authInfo);
            return true;
        }
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<TUser>()
            .eq(TUser::getToken, token);
        TUser tUser = itUserService.getOne(queryWrapper);
        if (ObjectUtil.isEmpty(tUser) || tUser.getStatus() != 0) {
            log.error("用户非法");
            throw new JeecgBoot401Exception("token已失效");
        }
        Long restTime = System.currentTimeMillis() / 1000 - tUser.getTokenValidTime().longValue();
        if (restTime >= VALID_TIME) {
            throw new JeecgBoot401Exception("token已失效");
        }
        AuthInfo authInfo = new AuthInfo();
        authInfo.setId(tUser.getId());
        authInfo.setUserName(tUser.getUserName());
        authInfo.setName(tUser.getName());
        stringRedisTemplate.opsForValue().set(tokenKey, JSON.toJSONString(authInfo), VALID_TIME - restTime, TimeUnit.SECONDS);
        CurrentUserUtil.set(authInfo);
        return true;
    }
}

过滤拦截,实现 WebMvcConfigurer 接口

@Configuration
public class ApiAuthConfiguration implements WebMvcConfigurer {
    @Resource
    private ApiInterceptor apiInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiInterceptor).addPathPatterns("/api/**")
            .excludePathPatterns("/api/formula/download");
    }
}

注意

如果项目里有多个类继承了 WebMvcConfigurer 那么只会有一个类生效。

实际使用-项目中拦截 Token

第一步

  • 自定义拦截器实现 HandlerInterceptor 接口
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Lazy
    @Resource
    private TUserClient userClient;

    /**
     * 通过请求token核验用户身份,获取用户id
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = SecurityUtils.getToken(request);
        LoginUser loginUser = userClient.getLoginUser(token);
        if (ObjectUtils.isNotEmpty(loginUser)){
            LoginUserUtils.set(loginUser);
            return true;
        } else {
            throw new IllegalStateException("User Token Is Illegal");
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LoginUserUtils.remove();
    }
}
  • 自定义获取 Token 的工具类
/**
 * SecurityUtils 工具类
 *
 */
public class SecurityUtils {

    /**
     * 根据request获取请求token
     */
    public static String getToken(HttpServletRequest request) {

        // 从 Hearder 中获取
        String header = request.getHeader(TokenConstants.AUTHENTICATION);

        // 从 Parameter 中获取
        if (StringUtils.isBlank(header)) {
            header = request.getParameter(TokenConstants.AUTHENTICATION);
        }

        // 从 Cookie 中获取
        if (StringUtils.isBlank(header)) {
            Cookie[] cookies = request.getCookies();
            if (ObjectUtils.isEmpty(cookies)) {
                throw new RuntimeException("Request Token Is Empty");
            }
            for (Cookie cookie : cookies) {
                if (Objects.equals(cookie.getName(), TokenConstants.AUTHENTICATION)) {
                    header = cookie.getValue();
                }
            }
        }

        // 都为空的情况
        if (StringUtils.isBlank(header)) {
            throw new RuntimeException("Request Token Is Empty");
        }

        if (!header.startsWith(TokenConstants.PREFIX)) {
            throw new RuntimeException("Request Token Bearer Is Empty");
        }

        return JwtUtils.getUserKey(header.substring(TokenConstants.PREFIX.length()));
    }

}
  • 自定义 JWT 工具类
/**
 * Jwt工具类
 */
public class JwtUtils {
    public static String secret = TokenConstants.SECRET;


    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    public static Claims parseToken(String token)
    {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * 根据令牌获取用户标识
     * 
     * @param token 令牌
     * @return 用户ID
     */
    public static String getUserKey(String token)
    {
        Claims claims = parseToken(token);
        return getValue(claims, SecurityConstants.USER_KEY);
    }

    /**
     * 根据身份信息获取键值
     * 
     * @param claims 身份信息
     * @param key 键
     * @return 值
     */
    public static String getValue(Claims claims, String key)
    {
        return Convert.toStr(claims.get(key), "");
    }
}
  • 自定义用户信息工具类
/**
 * LoginUserUtils 工具类
 */
public class LoginUserUtils {

    private LoginUserUtils() {}

    private static final ThreadLocal<LoginUser> CURRENT_USER = new ThreadLocal<>();

    public static void set(LoginUser loginUser) {
        CURRENT_USER.set(loginUser);
    }

    public static LoginUser get() {
        return CURRENT_USER.get();
    }

    public static void remove() {
        CURRENT_USER.remove();
    }
}
  • ThreadLocal

通过 ThreadLocal 为每一个访问程序的线程,提供独立的实例。

第二步

  • 启用拦截器,实现 WebMvcConfigurer 接口
@Configuration
public class LoginConfig implements WebMvcConfigurer {

    @Resource
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/api/**")
            .excludePathPatterns("/api/carbon/calculation/*");
    }

    /**
     * 显示 swagger-ui.html文档展示页,还必须注入 swagger 资源
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}
  • addInterceptors

方法为添加拦截的规则,如上所有访问路径为:/api/**都会被拦截。在拦截之外还可以添加额外的控制,在上述的拦截规则下,接口:/api/carbon/calculation/* 就不会被拦截。

  • addResourceHandlers

添加静态资源的映射,如果本地项目中有静态资源信息,这一块需要添加,否则无法访问。


文章作者: L Q
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 L Q !
  目录