1. 过滤器(Filter)
作用
请求预处理:在请求到达控制器之前进行预处理。
响应后处理:在响应返回客户端之前进行后处理。
通用功能:适用于跨多个控制器的通用功能,如日志记录、身份验证、字符编码设置等。
实现方式
实现 javax.servlet.Filter 接口。
使用 @Component 注解:让 Spring 管理该 Bean。
可选:使用 @WebFilter 注解。会自动注册
注册方式
自动注册:
使用 @Component 注解,Spring Boot 会自动将其注册到 Servlet 容器中。
手动注册:
使用 FilterRegistrationBean 手动注册过滤器。
2. 拦截器(Interceptor)
作用
请求预处理:在请求到达控制器之前进行预处理。
响应后处理:在控制器处理请求后,但在视图渲染之前进行后处理。
视图渲染后处理:在视图渲染完成后执行。
特定功能:适用于特定控制器或特定请求路径的功能,如权限检查、日志记录等。
实现方式
实现 org.springframework.web.servlet.HandlerInterceptor 接口。
注册方式
通过 WebMvcConfigurer 接口注册拦截器:
3、JWT令牌生成token的工具类
生成的token,是3段,用.连接。其中:前两段头部和载荷都是通过Base64进行编码的,后最后一段是将前两段连接在一起,然后在通过相应的加密算法(这里HS256)进行加密过后的字符串。
将
generateToken()方法和parseToken()方法标识为static静态,就可以直接在filter和intercepter用【类名.方法名】的调用jwt工具类生成token
package cn.swu.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
//利用jwt令牌生成token
@Component
public class JwtUtils {
// 密钥,用于签名和验证JWT
private static final String SECRET_KEY = "bHdmdHcKCg==";
// 过期时间,单位为毫秒,2小时
private static final long EXPIRATION_TIME = 2 * 60 * 60 * 1000;
/**
* 生成JWT令牌
* @return 生成的JWT令牌
*/
public static String generateToken(Map<String, Object> claims) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.addClaims(claims)
.setExpiration(expiryDate)
.compact();
}
/**
* 解析JWT令牌
*
* @param token JWT令牌
* @return 解析后的Claims对象
* @throws RuntimeException 如果令牌无效或过期
*/
public static Claims parseToken(String token) {
try {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new RuntimeException("Invalid or expired JWT token", e);
}
}
/**
* 从Claims中获取主题
*
* @param claims Claims对象
* @return 主题
*/
public String getSubjectFromClaims(Claims claims) {
return claims.getSubject();
}
}
package cn.swu.filter;
import cn.swu.utils.CurrentHolder;
import cn.swu.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
;
import java.io.IOException;
@Slf4j
//@Component
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
//@Autowired
// private JwtUtils jwtUtils;//静态方法不用依赖注入初始化
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(JwtUtils.class);
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取请求路径
String requestURI = request.getRequestURI();
// 判断请求路径是否是登录请求,如果是登录请求,直接放行
if (requestURI.startsWith("/login")) {
filterChain.doFilter(request, response);
return;
}
//获取请求头中的token
String token = request.getHeader("token");
//判断token是否为空,如果为空,则提示用户登录,返回401状态码
if (token == null || token.isEmpty()) {
// System.out.println(token);
response.setStatus(401);
response.getWriter().write("请先登录");
return;
}
//如果token不为空,则解析校验令牌oken,如果解析失败,则提示用户登录,返回401状态码
//如果校验成功,则放行
try {
Claims claims = JwtUtils.parseToken(token);
//解析token,获取ID,将ID设置给当前线程,方便AOP获取当前线程中的用户登录ID
Integer id = Integer.valueOf(claims.get("id").toString());
CurrentHolder.setCurrentId(id);//静态方法可直接用类名来调用
log.info("当前线程中的用户登录ID为:{}", id);
filterChain.doFilter(request, response);
//帮助AOP获得当前用户ID之后,过滤器放行该请求之后其他层处理完该请求,再进行业务操作,删除当前thread的ThreadLocal中的id值
CurrentHolder.remove();
} catch (Exception e) {
response.setStatus(401);
response.getWriter().write("请先登录");
}
}
}
package cn.swu.interceptor;
import cn.swu.utils.CurrentHolder;
import cn.swu.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle");
/* //获取请求路径
String requestURI = request.getRequestURI();
// 判断请求路径是否是登录请求,如果是登录请求,直接放行
if (requestURI.startsWith("/login")) {
return true;
}*/
//获取请求头中的token
String token = request.getHeader("token");
//判断token是否为空,如果为空,则提示用户登录,返回401状态码
if (token == null || token.length() == 0) {
response.setStatus(401);
response.getWriter().write("请先登录");
return false;
}
//如果token不为空,则解析校验令牌oken,如果解析失败,则提示用户登录,返回401状态码
//如果校验成功,则放行
try {
Claims claims = jwtUtils.parseToken(token);
//解析token,获取ID,将ID设置给当前线程,方便AOP获取当前线程中的用户登录ID
Integer id = Integer.valueOf(claims.get("id").toString());
CurrentHolder.setCurrentId(id);//静态方法可直接用类名来调用
log.info("当前线程中的用户登录ID为:{}", id);
return true;
} catch (Exception e) {
response.setStatus(401);
response.getWriter().write("请先登录");
return false;
}
// true放行, false拦截
}
// 过滤器放行之后再进行业务操作,删除当前thread的ThreadLocal中的id值
//请求处理完成后再执行该方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//帮助AOP获得当前用户ID之后,过滤器放行该请求之后再进行业务操作,删除当前thread的ThreadLocal中的id值
CurrentHolder.remove();
}
}
intercepter注册需要额外的配置类
WebConfig
package cn.swu.config;
import cn.swu.interceptor.TestInterceptor;
import cn.swu.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TestInterceptor testInterceptor;
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) { // 拦截器注册
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login","/css/**","/js/**","/img/**","/fonts/**","/plugins/**");
}
}
区别
过滤器(Filter):
适用于所有请求,包括静态资源。
使用 @Component 注解或 FilterRegistrationBean 注册。
适用于通用功能,如身份验证、日志记录等。
拦截器(Interceptor):
仅适用于 Spring MVC 处理的请求。
使用 WebMvcConfigurer 接口注册。
适用于特定控制器或特定请求路径的功能,如权限检查、日志记录等。
注册FIlter时,
@Component注解实现浅层原理
过滤器生效的原因
TokenFilter 类被标记为 @Component,因此 Spring Boot 会自动将其注册到 Servlet 容器中。具体步骤如下:
类扫描:
Spring Boot 在启动时会扫描带有 @Component 注解的类。
TokenFilter 类被标记为 @Component,因此会被扫描到。
Bean 创建:
Spring 会创建 TokenFilter 的 Bean 实例。
自动注册:
由于 TokenFilter 实现了 javax.servlet.Filter 接口,Spring Boot 会自动将其注册到 Servlet 容器中,使其能够拦截 HTTP 请求。
@WebFilter注解如何被 扫描到
在启动类上标识 @ServletComponentScan // 扫描Servlet注解, 如:@WebFilter