
在Spring Boot应用中,当我们需要为API接口添加JWT认证时,通常会自定义一个JWT认证过滤器,并使用HttpSecurity.addFilterBefore()方法将其添加到Spring Security的过滤器链中。例如:
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(customJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// ... 其他配置
}这种做法虽然简单,但存在一个问题:customJwtAuthenticationFilter会拦截所有进入Spring Security过滤器的请求。如果我们的应用既有需要认证的API(如/api/**),也有公开访问的页面或接口(如/login, /, /public/**),那么即使是公开接口,也会被JWT过滤器尝试处理,这可能导致不必要的性能开销或逻辑复杂性。
理想情况下,我们希望JWT过滤器只对那些明确需要JWT认证的路径(例如,所有以/api/开头的路径)生效,而对其他路径则直接放行或交由其他过滤器处理。
Spring Security提供了AbstractAuthenticationProcessingFilter,这是一个专门用于处理特定认证流程的抽象基类。它允许我们通过传入一个RequestMatcher实例来精确控制过滤器所处理的请求。当请求的URL与RequestMatcher匹配时,AbstractAuthenticationProcessingFilter才会尝试执行认证逻辑。
步骤一:修改 CustomJwtAuthenticationFilter
让你的JWT认证过滤器继承AbstractAuthenticationProcessingFilter,并在构造函数中接收一个RequestMatcher实例。同时,你需要重写attemptAuthentication()方法,将你原有的JWT解析和认证逻辑放入其中。
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 假设你有一个JwtTokenProvider来处理JWT的生成和验证
// import com.yourpackage.security.JwtTokenProvider;
public class CustomJwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final UserDetailsService userDetailsService;
// private final JwtTokenProvider jwtTokenProvider; // 假设你有一个JWT工具类
// 构造函数:传入RequestMatcher和AuthenticationManager
public CustomJwtAuthenticationFilter(
RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationManager authenticationManager,
UserDetailsService userDetailsService
/*, JwtTokenProvider jwtTokenProvider */) {
super(requiresAuthenticationRequestMatcher); // 指定哪些请求需要此过滤器处理
setAuthenticationManager(authenticationManager); // 设置认证管理器
this.userDetailsService = userDetailsService;
// this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// 1. 从请求头中提取JWT令牌
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
// 如果没有Bearer Token,或者格式不正确,则抛出认证异常
// AbstractAuthenticationProcessingFilter 会捕获此异常并调用 AuthenticationFailureHandler
throw new BadCredentialsException("Missing or invalid Authorization header");
}
String jwtToken = authorizationHeader.substring(7); // 移除 "Bearer " 前缀
// 2. 验证JWT令牌并获取用户信息
// 实际项目中,这里会调用你的JwtTokenProvider来验证令牌并解析出用户名
// 示例:
// if (!jwtTokenProvider.validateToken(jwtToken)) {
// throw new BadCredentialsException("Invalid JWT token");
// }
// String username = jwtTokenProvider.getUsernameFromToken(jwtToken);
// 简化示例,直接假设从JWT中解析出用户名 "testuser"
String username = "testuser"; // 实际应从JWT中解析
// 3. 根据用户名加载用户详情
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 4. 创建一个认证令牌并交由AuthenticationManager进行认证
// 注意:这里通常不需要再次调用authenticationManager.authenticate(),
// 因为JWT本身就是一种认证凭证。我们直接创建一个已认证的Authentication对象。
// 如果你的JWT验证逻辑非常复杂,需要AuthenticationManager的Provider来处理,
// 则可以构造一个UsernamePasswordAuthenticationToken或其他Token,然后调用getAuthenticationManager().authenticate()
// 但对于多数JWT场景,直接返回一个已认证的Token即可。
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// 将请求详情设置到Authentication对象中,以便后续的WebAuthenticationDetailsSource使用
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
return authenticationToken; // 返回已认证的Authentication对象
}
// 可以在这里重写 successfulAuthentication 和 unsuccessfulAuthentication
// 以处理认证成功或失败后的逻辑,例如记录日志、设置响应头等。
// @Override
// protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// SecurityContextHolder.getContext().setAuthentication(authResult);
// chain.doFilter(request, response);
// }
// @Override
// protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
// // 可以在这里自定义失败响应
// super.unsuccessfulAuthentication(request, response, failed);
// }
}步骤二:在 WebSecurityConfigurerAdapter 中配置过滤器
在你的安全配置类中,将CustomJwtAuthenticationFilter声明为一个Spring Bean,并在configure(HttpSecurity http)方法中将其添加到过滤器链。关键在于创建CustomJwtAuthenticationFilter实例时,传入正确的RequestMatcher。
import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService; // 你的UserDetailsService实现
// @Autowired
// private JwtTokenProvider jwtTokenProvider; // 你的JWT工具类
// 假设你有一个JWT认证入口点,处理未认证的请求
// @Autowired
// private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 声明 CustomJwtAuthenticationFilter 为 Spring Bean
@Bean
public CustomJwtAuthenticationFilter customJwtAuthenticationFilter() throws Exception {
// 定义需要JWT过滤器处理的URL模式
// 示例1: 只匹配 /api/**
RequestMatcher protectedUrlMatcher = new AntPathRequestMatcher("/api/**");
// 示例2: 匹配多个URL模式 (如 /api/users/** 和 /api/products/**)
// List<String> protectedPaths = Arrays.asList("/api/users/**", "/api/products/**");
// RequestMatcher protectedUrlMatcher = new OrRequestMatcher(
// protectedPaths.stream()
// .map(AntPathRequestMatcher::new)
// .collect(Collectors.toList())
// );
// 实例化 CustomJwtAuthenticationFilter,传入RequestMatcher、AuthenticationManager和UserDetailsService
// 注意:authenticationManagerBean() 在这里直接调用会抛出异常,因为它需要Spring上下文初始化。
// 正确的做法是将其作为参数注入到 @Bean 方法中,或者在 configure(HttpSecurity) 中获取。
// 这里通过在 @Bean 方法签名中添加 AuthenticationManager authenticationManager 来注入
return new CustomJwtAuthenticationFilter(
protectedUrlMatcher,
authenticationManagerBean(), // 获取AuthenticationManager实例
userDetailsService
/*, jwtTokenProvider */);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用CSRF
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // JWT通常是无状态的
.and()
.exceptionHandling()
// .authenticationEntryPoint(jwtAuthenticationEntryPoint) // 处理未认证的请求
// .accessDeniedPage("/403") // 处理无权限的请求
.and()
.authorizeRequests()
// 确保被JWT过滤器处理的路径需要认证
.antMatchers("/api/**").authenticated()
// 其他路径可以公开访问
.antMatchers("/login", "/", "/public/**").permitAll()
.anyRequest().authenticated() // 默认所有其他请求都需要认证
.and()
// 将自定义的JWT过滤器添加到UsernamePasswordAuthenticationFilter之前
.addFilterBefore(customJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// .formLogin() // 如果你还需要表单登录,可以继续配置
// .loginPage("/login")
// .defaultSuccessUrl("/users")
// .failureUrl("/login?error=true")
// .permitAll()
// .and()
// .logout()
// .logoutSuccessUrl("/")
// .permitAll();
}
}通过以上配置,你的JWT过滤器将只会对/api/**路径下的请求进行处理,而其他路径(如/login、/等)将不再经过JWT过滤器的认证逻辑,从而实现更精准、高效的安全控制。
以上就是Spring Boot Security:实现JWT过滤器对特定URL路径的精准控制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号