Spring Security 6中requestMatchers配置引发API返回403 Forbidden问题
Spring Boot + Spring Security 权限异常排查:API执行成功但返回403
问题现象
基于SpringBoot和Spring Security开发项目时,配置/api/**路径需要hasRole("USER")或authenticated权限后:
- API实际操作成功(例如删除用户时数据库记录已移除)
- Postman始终返回403 Forbidden
- 改为
permitAll()后一切正常
日志显示携带Bearer Token的用户已成功认证,但存在第二次匿名请求访问/api路径,触发Http403ForbiddenEntryPoint拒绝访问。
核心代码与问题分析
SecurityConfig.kt
@Configuration @EnableWebSecurity class SecurityConfig( private val authenticationProvider: AuthenticationProvider ) { @Bean fun filterChain( http: HttpSecurity, jwtFilter: JwtFilter ): DefaultSecurityFilterChain = http .csrf { it.disable() } .authorizeHttpRequests { auth -> auth .requestMatchers("/auth/**").permitAll() .requestMatchers("/api/**").hasRole("USER") // authenticated()同样不生效 .anyRequest().fullyAuthenticated() } .sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } .authenticationProvider(authenticationProvider) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java) .build() }
JwtFilter.kt(关键漏洞)
@Component class JwtFilter( private val userDetailsService: CustomUserDetailsService, private val tokenService: TokenService ) : OncePerRequestFilter() { override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain ) { val authHeader: String? = request.getHeader("Authorization") if (authHeader.doesNotContainBearerToken()) { logger.debug("No Bearer token found in the request") filterChain.doFilter(request, response) return } val jwtToken = authHeader!!.extractTokenValue() logger.info("Token: $jwtToken") val email = tokenService.extractEmail(jwtToken) if (email != null && SecurityContextHolder.getContext().authentication == null) { logger.debug("Attempting to authenticate user: $email") val foundUser = userDetailsService.loadUserByUsername(email) if (tokenService.isValid(jwtToken, foundUser)) { updateContext(foundUser, request) logger.info("Token is valid. User authenticated: $email") } else { logger.warn("Invalid token for user: $email") } filterChain.doFilter(request, response) } // 问题:不满足上方if条件时,未调用filterChain.doFilter,请求被中断 val auth = SecurityContextHolder.getContext().authentication logger.info("Authentication status: ${auth?.isAuthenticated}, User: ${auth?.name}, Authorities: ${auth?.authorities}") } // 其他方法省略... }
问题根源
JwtFilter的逻辑存在漏洞:仅当email != null && SecurityContextHolder.getContext().authentication == null条件满足时,才会调用filterChain.doFilter继续执行后续流程;若不满足该条件(比如请求已认证、或解析出的email为null),请求会卡在当前过滤器,无法返回响应。
此时Postman因未收到响应会发起重试,而重试请求可能因无状态模式下SecurityContext未保留、或Token携带异常,导致第二次请求以匿名身份访问/api/**路径,触发403拒绝。同时第一次请求的业务逻辑已执行(如数据库操作),因此出现"操作成功但返回403"的矛盾现象。
修复方案
调整JwtFilter逻辑,确保所有场景下都调用filterChain.doFilter,将过滤器链调用移至方法末尾,避免请求中断:
@Component class JwtFilter( private val userDetailsService: CustomUserDetailsService, private val tokenService: TokenService ) : OncePerRequestFilter() { override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain ) { val authHeader: String? = request.getHeader("Authorization") if (!authHeader.doesNotContainBearerToken()) { val jwtToken = authHeader.extractTokenValue() logger.info("Token: $jwtToken") val email = tokenService.extractEmail(jwtToken) if (email != null && SecurityContextHolder.getContext().authentication == null) { logger.debug("Attempting to authenticate user: $email") val foundUser = userDetailsService.loadUserByUsername(email) if (tokenService.isValid(jwtToken, foundUser)) { updateContext(foundUser, request) logger.info("Token is valid. User authenticated: $email") } else { logger.warn("Invalid token for user: $email") } } } else { logger.debug("No Bearer token found in the request") } val auth = SecurityContextHolder.getContext().authentication logger.info("Authentication status: ${auth?.isAuthenticated}, User: ${auth?.name}, Authorities: ${auth?.authorities}") // 确保所有请求都继续执行过滤器链 filterChain.doFilter(request, response) } // 其他方法保持不变... }
额外验证点
- 确认CustomUserDetailsService中
roles("USER")生成的权限为ROLE_USER,与hasRole("USER")的自动添加ROLE_前缀逻辑匹配。 - 检查TokenService生成的Token是否正确包含用户信息,签名、有效期均正常。
- 关闭Postman自动重试功能,避免重复请求干扰排查。
内容的提问来源于stack exchange,提问作者Rostislav Kosyakin
相关产品推荐
相关产品推荐

