使用OncePerRequestFilter自定义认证致Spring异常映射为403的解决方法
Spring Security自定义过滤器异常响应码被映射为403的解决方法
问题背景
- 自定义Telegram认证过滤器
TelegramAuthenticationFilter,从请求头获取认证数据并将认证对象放入安全上下文完成用户认证 - 当业务逻辑抛出异常(如数据库未找到对应记录)时,响应码被映射为403而非预期的500
- 调试发现
ExceptionTranslationFilter先捕获到业务逻辑异常,但后续AuthorizationFilter的doFilterMethod()抛出AccessDeniedException,最终导致响应码变为403
现有代码实现
自定义认证过滤器
package com.fnot.backend.config.spring.security import com.fnot.backend.config.external.telegram.TelegramConfig import com.fnot.backend.domain.external.telegram.bot.service.TelegramBotService import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter @Component class TelegramAuthenticationFilter( telegramConfig: TelegramConfig, private val telegramBotService: TelegramBotService ): OncePerRequestFilter() { private val botToken = telegramConfig.botToken override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain ) { val authHeader = request.getHeader("TG-Authorization") if(authHeader == null) { filterChain.doFilter(request, response) return } val tmaAuthData = TmaAuthData(authHeader, botToken) if (tmaAuthData.isValid()) { val telegramUserData = tmaAuthData.getTelegramUserData() val user = telegramBotService.registerOrUpdateUserTma(telegramUserData) val authToken = TelegramAuthenticationToken(user.id!!) SecurityContextHolder.getContext().authentication = authToken } filterChain.doFilter(request, response) } }
Security配置类
package com.fnot.backend.config.spring.security import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.UrlBasedCorsConfigurationSource import org.springframework.web.filter.CorsFilter @Configuration @EnableWebSecurity class SecurityConfig( private val telegramAuthenticationFilter: TelegramAuthenticationFilter ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http .csrf { it.disable() } .cors { } // Enable CORS .authorizeHttpRequests { auth -> auth.requestMatchers("/").permitAll() .anyRequest().authenticated() } .addFilterBefore(telegramAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java) return http.build() } @Bean fun corsFilter(): CorsFilter { val source = UrlBasedCorsConfigurationSource() val config = CorsConfiguration() config.allowCredentials = true config.addAllowedOriginPattern("*") // Allow all origins config.addAllowedHeader("*") // Allow all headers config.addAllowedMethod("*") // Allow all methods source.registerCorsConfiguration("/**", config) return CorsFilter(source) } }
问题原因
当自定义过滤器的认证逻辑抛出业务异常时,SecurityContextHolder可能未被设置有效认证对象,后续AuthorizationFilter检查请求认证状态时,会因无有效认证信息抛出AccessDeniedException,覆盖了原本的业务异常,最终返回403响应码。
解决方案
修改自定义过滤器
在认证逻辑块添加异常捕获,清除安全上下文后重新抛出异常,确保业务异常能被Spring异常处理器正常处理:
package com.fnot.backend.config.spring.security import com.fnot.backend.config.external.telegram.TelegramConfig import com.fnot.backend.domain.external.telegram.bot.service.TelegramBotService import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter @Component class TelegramAuthenticationFilter( telegramConfig: TelegramConfig, private val telegramBotService: TelegramBotService ): OncePerRequestFilter() { private val botToken = telegramConfig.botToken override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain ) { val authHeader = request.getHeader("TG-Authorization") if(authHeader == null) { filterChain.doFilter(request, response) return } val tmaAuthData = TmaAuthData(authHeader, botToken) if (tmaAuthData.isValid()) { try { val telegramUserData = tmaAuthData.getTelegramUserData() val user = telegramBotService.registerOrUpdateUserTma(telegramUserData) val authToken = TelegramAuthenticationToken(user.id!!) SecurityContextHolder.getContext().authentication = authToken } catch (e: Exception) { // 清除安全上下文,避免后续过滤器误判 SecurityContextHolder.clearContext() // 抛出异常交由全局处理器处理 throw e } } filterChain.doFilter(request, response) } }
添加全局异常处理器
创建全局异常处理器,统一处理所有未捕获异常,返回500响应码:
package com.fnot.backend.config.exception import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(Exception::class) fun handleAllExceptions(e: Exception): ResponseEntity<Map<String, String>> { val errorResponse = mapOf( "message" to e.message ?: "服务器内部错误", "status" to HttpStatus.INTERNAL_SERVER_ERROR.value().toString() ) return ResponseEntity(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR) } }
关键说明
- 捕获认证逻辑异常后先清除安全上下文,避免后续安全过滤器因无效上下文抛出权限异常
- 重新抛出异常,让全局异常处理器统一捕获并返回500响应
- 全局异常处理器确保所有未处理异常都能得到一致的错误响应格式和状态码
内容的提问来源于stack exchange,提问作者Arthur Klezovich
相关产品推荐
相关产品推荐

