如何避免Spring WebFlux过滤器被调用两次?

问题描述 投票:0回答:1

经过长时间的详尽搜索,寻求帮助。我正在

Spring WebFlux 和 Spring Security 中编写一个 
JwtTokenFilter 问题是这个过滤器在单个请求上被调用两次。过滤代码如下。

    @Component
class JwtTokenAuthenticationFilter(
    @Autowired val tokenProvider: JwtTokenProvider
) : WebFilter {
    private val HEADER_PREFIX = "Bearer "

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void?> {
        val token = resolveToken(exchange.request)
        return if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
            val authentication: Authentication = tokenProvider.getAuthentication(token)
            chain.filter(exchange)
                .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
        } else
            chain.filter(exchange)
    }

    private fun resolveToken(request: ServerHttpRequest): String? {
        val bearerToken = request.headers.getFirst(HttpHeaders.AUTHORIZATION)
        return if (StringUtils.hasText(bearerToken) && bearerToken!!.startsWith(HEADER_PREFIX)) {
            bearerToken.substring(7)
        } else null
    }
}

这个问题可以通过删除过滤器上的 @Component 注释来避免,但这意味着我必须在路径中创建这个对象及其所有依赖项,这完全剥夺了 Spring 依赖反转 的优势。因此,我想保留使用 Spring 注解来完成工作。然而,这会产生一个问题,即过滤器注册在两个地方,即 servlet 容器和 Spring Security。

this文章中介绍的解决方案谈到了它。

但是找不到将该文章中的解决方案应用于 WebFilter 的方法,因为该文章中的解决方案适用于 servlet Filters。

那么,有没有办法避免这个bean被servlet容器调用,而只允许它被Spring Security调用。或者您看到的代码中存在其他问题?

spring spring-boot spring-mvc spring-security spring-webflux
1个回答
0
投票

通过删除@Component,过滤器将不会被servlet容器自动拾取。

    @Slf4j
    //@Component -> Remove this
    @RequiredArgsConstructor
    public class JwtAuthenticationFilter implements WebFilter {
    
        private final JwtTokenProvider jwtTokenProvider;
    
        @NonNull
        @Override
        public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
            log.debug("Processing request: {} {} at {}", exchange.getRequest().getMethod(), exchange.getRequest().getPath(), System.currentTimeMillis());
            String token = resolveToken(exchange.getRequest());
            if (StringUtils.hasText(token) && this.jwtTokenProvider.isTokenValid(token)) {
                return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.getAuthentication(token)));
            }
    
            return chain.filter(exchange);
        }
    
        private String resolveToken(ServerHttpRequest request) {
            String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
}

这避免了在 Spring 上下文中的多次注册,并确保过滤器仅应用于 Spring Security 过滤器链内。

@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(jwtTokenProvider); // add it here
        return http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .cors(ServerHttpSecurity.CorsSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .logout(ServerHttpSecurity.LogoutSpec::disable)
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers(WHITE_LIST_URL).permitAll()
                        .anyExchange().authenticated()
                )
                .addFilterAt(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .build();
    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.