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