
在spring boot应用中集成jwt(json web token)进行认证是常见的做法。然而,默认情况下,如果我们将自定义的jwt认证过滤器直接添加到spring security的过滤器链中,它可能会拦截所有传入的http请求。在某些场景下,我们可能只希望对特定路径(例如restful api接口,通常以/api/开头)进行jwt认证,而让其他路径(如静态资源、登录页面、公共接口等)不受jwt过滤器的影响。本文将介绍如何通过扩展abstractauthenticationprocessingfilter并结合requestmatcher接口来实现这一目标。
1. 理解核心组件:AbstractAuthenticationProcessingFilter与RequestMatcher
Spring Security提供了AbstractAuthenticationProcessingFilter作为处理特定认证请求的抽象基类。这个过滤器在内部使用一个RequestMatcher来决定当前请求是否需要进行认证处理。如果RequestMatcher的matches()方法返回true,则过滤器会尝试处理认证;如果返回false,则直接放行,不进行认证处理。这正是我们实现特定URL过滤的关键。
2. 构建自定义JWT认证过滤器
我们的自定义JWT认证过滤器CustomJwtAuthenticationFilter需要继承AbstractAuthenticationProcessingFilter。关键在于其构造函数,它必须接收一个RequestMatcher实例。
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomJwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 假设您有一个JWT解析和验证的服务
private final JwtTokenProvider jwtTokenProvider;
public CustomJwtAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, JwtTokenProvider jwtTokenProvider, AuthenticationManager authenticationManager) {
super(requiresAuthenticationRequestMatcher); // 将RequestMatcher传递给父类
this.jwtTokenProvider = jwtTokenProvider;
setAuthenticationManager(authenticationManager); // 设置认证管理器
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// 从请求头中获取JWT Token
String token = jwtTokenProvider.resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
// 如果Token有效,则构建一个认证对象
// 这里的AuthenticationToken需要根据您的JWT实现来定义
// 例如,可以是一个包含用户信息的UsernamePasswordAuthenticationToken
Authentication authentication = jwtTokenProvider.getAuthentication(token);
return getAuthenticationManager().authenticate(authentication); // 交给AuthenticationManager进行认证
}
// 如果没有Token或Token无效,则抛出认证异常
throw new AuthenticationException("JWT Token is missing or invalid") {};
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 认证成功后,将认证信息设置到SecurityContext中,并继续过滤器链
// SecurityContextHolder.getContext().setAuthentication(authResult); // AbstractAuthenticationProcessingFilter 内部会处理
chain.doFilter(request, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
// 认证失败处理,例如返回401 Unauthorized
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Authentication Failed: " + failed.getMessage());
}
}注意:上述代码中的JwtTokenProvider是一个假设的服务,用于解析和验证JWT。getAuthentication()方法应根据您的JWT结构返回一个Authentication对象,通常是UsernamePasswordAuthenticationToken。
3. 实现自定义的RequestMatcher
为了让过滤器只作用于/api/**路径,我们需要创建一个RequestMatcher的实现类,其matches()方法在请求路径匹配/api/**时返回true。
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import jakarta.servlet.http.HttpServletRequest;
public class ApiPathRequestMatcher implements RequestMatcher {
private final AntPathRequestMatcher apiPathMatcher;
public ApiPathRequestMatcher(String pattern) {
this.apiPathMatcher = new AntPathRequestMatcher(pattern);
}
@Override
public boolean matches(HttpServletRequest request) {
// 如果请求路径匹配指定的模式,则返回true,表示需要进行认证处理
return apiPathMatcher.matches(request);
}
}这里我们使用了Spring Security内置的AntPathRequestMatcher来方便地匹配Ant风格的路径模式。
4. 在Spring Security配置中集成过滤器
最后一步是将CustomJwtAuthenticationFilter和ApiPathRequestMatcher集成到Spring Security的配置中。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; // 或者其他认证入口点
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
private final JwtTokenProvider jwtTokenProvider; // 您的JWT提供者服务
private final BasicAuthenticationEntryPoint jwtAuthenticationEntryPoint; // 认证入口点,用于未认证访问时的处理
public WebSecurityConfig(JwtTokenProvider jwtTokenProvider, BasicAuthenticationEntryPoint jwtAuthenticationEntryPoint) {
this.jwtTokenProvider = jwtTokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public CustomJwtAuthenticationFilter customJwtAuthenticationFilter(AuthenticationManager authenticationManager) {
// 创建只匹配/api/**路径的请求匹配器
ApiPathRequestMatcher apiMatcher = new ApiPathRequestMatcher("/api/**");
return new CustomJwtAuthenticationFilter(apiMatcher, jwtTokenProvider, authenticationManager);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, CustomJwtAuthenticationFilter customJwtAuthenticationFilter) throws Exception {
http.csrf().disable() // 禁用CSRF
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // JWT通常是无状态的
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint) // 未认证访问时的处理
.accessDeniedPage("/403") // 访问被拒绝时的页面
.and()
.authorizeRequests()
// /api/** 路径将由我们的CustomJwtAuthenticationFilter进行认证,
// 但这里仍然需要配置其授权规则(例如,认证后才能访问)
.antMatchers("/api/**").authenticated()
// 其他路径允许所有访问
.anyRequest().permitAll()
.and()
// 如果您的应用同时有表单登录,可以保留以下配置
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/users")
.failureUrl("/login?error=true")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll()
.and()
// 将我们的自定义JWT过滤器添加到UsernamePasswordAuthenticationFilter之前
// 这样JWT认证会在Spring Security的默认表单认证之前执行
.addFilterBefore(customJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}5. 注意事项与总结
- 过滤器顺序:通过addFilterBefore(customJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class),我们确保了自定义的JWT过滤器在Spring Security默认的用户名密码认证过滤器之前执行。这对于API认证场景是合适的,因为我们希望首先尝试JWT认证。
- authorizeRequests()与自定义过滤器:即使CustomJwtAuthenticationFilter负责认证/api/**路径,authorizeRequests().antMatchers("/api/**").authenticated()仍然是必要的。前者处理“认证”过程(验证凭据并识别用户),后者处理“授权”过程(认证后用户是否有权限访问该资源)。如果JWT认证成功,用户将被认证,然后authorizeRequests()会根据其认证状态(authenticated())允许访问。
- 无状态会话:JWT认证通常是无状态的,因此将sessionCreationPolicy(SessionCreationPolicy.STATELESS)设置为无状态是最佳实践,这会告诉Spring Security不要创建或使用HTTP会话来存储安全上下文。
- 异常处理:authenticationEntryPoint用于处理未认证用户尝试访问受保护资源的情况。accessDeniedPage用于处理已认证用户但无权访问特定资源的情况。
- JwtTokenProvider和AuthenticationManager:在实际应用中,您需要实现JwtTokenProvider来处理JWT的生成、解析和验证,并配置AuthenticationManager来处理认证逻辑(例如,通过UserDetailsService加载用户详情)。
通过上述配置,您的Spring Boot应用将能够精确地控制JWT认证过滤器的作用范围,仅对/api/**等指定路径进行JWT认证,从而实现更高效、更安全的API访问控制。










