经过长时间的详尽搜索,寻求帮助。我正在
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调用。或者您看到的代码中存在其他问题?
通过删除@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();
}
}