You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SecurityWebFilterChain中IllegalArgumentException异常处理失效问题

问题分析与解决方案

问题描述

我使用zalando problem spring webflux来简化异常处理。

@Configuration
@Import(SecurityProblemSupport::class)
class SecurityConfig{
    @Autowired
    lateinit var problemSupport: SecurityProblemSupport

    //...
    @Bean
    fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http {
        //....
        exceptionHandling {
            authenticationEntryPoint = problemSupport
            accessDeniedHandler = problemSupport
        }
        authorizeExchange {
            //...
            authorize(pathMatchers("/{id}/**"), customCheck())
            //...
        }
   }
   
   fun customCheck():ReactiveAuthenticationManager<AuthenticationContext>{
      // 此处抛出运行时IllegalArgumentException异常
   }
}

我已为IllegalArgumentException创建了异常处理器,它能正常处理控制器抛出的该异常。

预期行为

通过异常处理器(已将accessDeniedHandler配置为zalando全局问题处理器)处理IllegalArgumentException,返回400状态码。

实际行为

但实际返回的是500内部错误。


原因分析

Spring Security的过滤器链执行顺序早于WebFlux控制器,授权阶段抛出的IllegalArgumentException不会被全局的WebFlux异常处理器(包括Zalando的Problem处理器)捕获。你配置的accessDeniedHandler仅处理AccessDeniedException及其子类的授权拒绝场景,非Security标准异常会被默认逻辑转为500内部错误。


解决方案

方案1:扩展SecurityProblemSupport处理自定义异常

自定义SecurityProblemSupport子类,在其中捕获IllegalArgumentException并转换为400响应:

class CustomSecurityProblemSupport : SecurityProblemSupport() {
    override fun handle(exchange: ServerWebExchange, ex: AccessDeniedException): Mono<Void> {
        return if (ex.cause is IllegalArgumentException) {
            val badRequestProblem = Problem.builder()
                .withStatus(Status.BAD_REQUEST)
                .withTitle("非法请求")
                .withDetail((ex.cause as IllegalArgumentException).message)
                .build()
            writeProblem(exchange, badRequestProblem)
        } else {
            super.handle(exchange, ex)
        }
    }

    override fun onAuthenticationFailure(exchange: ServerWebExchange, ex: AuthenticationException): Mono<Void> {
        return if (ex.cause is IllegalArgumentException) {
            val badRequestProblem = Problem.builder()
                .withStatus(Status.BAD_REQUEST)
                .withTitle("认证失败")
                .withDetail((ex.cause as IllegalArgumentException).message)
                .build()
            writeProblem(exchange, badRequestProblem)
        } else {
            super.onAuthenticationFailure(exchange, ex)
        }
    }
}

然后在SecurityConfig中替换导入的类:

@Configuration
@Import(CustomSecurityProblemSupport::class)
class SecurityConfig{
    @Autowired
    lateinit var problemSupport: CustomSecurityProblemSupport

    // 其余代码不变
}

方案2:抛出Security标准异常并映射状态码

在自定义校验中抛出Spring Security的AuthorizationDeniedException(或自定义继承AccessDeniedException的异常),再通过Zalando处理器映射到400状态:

fun customCheck(): ReactiveAuthenticationManager<AuthenticationContext> {
    return ReactiveAuthenticationManager { context ->
        // 执行你的校验逻辑
        if (校验不通过) {
            Mono.error(AuthorizationDeniedException("参数非法,拒绝访问"))
        } else {
            Mono.just(context.authentication)
        }
    }
}

配置异常映射:

@Bean
fun problemConfig(): ProblemHandling {
    return ProblemHandling.builder()
        .withExceptionMapping(AuthorizationDeniedException::class.java) { ex ->
            Problem.builder()
                .withStatus(Status.BAD_REQUEST)
                .withDetail(ex.message)
                .build()
        }
        .build()
}

方案3:全局ErrorWebExceptionHandler接管所有异常

确保Zalando的ProblemErrorWebExceptionHandler能处理Security阶段的异常,可自定义全局异常处理器统一处理:

@Component
class GlobalErrorWebExceptionHandler(
    problemProperties: ProblemProperties,
    messageSource: MessageSource,
    serverCodecConfigurer: ServerCodecConfigurer
) : ProblemErrorWebExceptionHandler(problemProperties, messageSource, serverCodecConfigurer) {

    override fun handle(exchange: ServerWebExchange, throwable: Throwable): Mono<Void> {
        val handledThrowable = if (throwable is IllegalArgumentException) {
            // 转换为Problem格式
            Problem.builder()
                .withStatus(Status.BAD_REQUEST)
                .withDetail(throwable.message)
                .build()
        } else {
            throwable
        }
        return super.handle(exchange, handledThrowable)
    }
}

内容的提问来源于stack exchange,提问作者Hantsy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 18:22:05