Refactor AdditionalWebFilter load (#5349)

#### What type of PR is this?

/kind improvement
/area core
/milestone 2.13.x

#### What this PR does / why we need it:

This PR creates AdditionalWebFilterChainProxy to call all additional filters instead of using SecurityWebFilterChain.

Please note that:
- the AdditionalWebFilterChainProxy should be executed before `org.springframework.security.web.server.WebFilterChainProxy`.
- I don't change `UsernamePasswordAuthenticator` because of <https://github.com/halo-dev/halo/pull/5348>. The authenticator should be in Security scope instead of a standalone webfilter.

See https://github.com/halo-dev/halo/issues/5300#issuecomment-1933436652 for more.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/5300

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/5362/head
John Niang 2024-02-18 12:16:15 +08:00 committed by GitHub
parent caf3e66e8c
commit 9178ad0e22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 70 deletions

View File

@ -15,6 +15,7 @@ import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@ -40,6 +41,8 @@ import run.halo.app.console.WebSocketRequestPredicate;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.webfilter.AdditionalWebFilterChainProxy;
@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
@ -200,4 +203,23 @@ public class WebFluxConfig implements WebFluxConfigurer {
ProxyFilter ucProxyFilter() {
return new ProxyFilter("/uc/**", haloProp.getUc().getProxy());
}
/**
* Create a WebFilterChainProxy for all AdditionalWebFilters.
*
* <p>The reason why the order is -101 is that the current
* AdditionalWebFilterChainProxy should be executed before WebFilterChainProxy
* and the order of WebFilterChainProxy is -100.
*
* <p>See {@code org.springframework.security.config.annotation.web.reactive
* .WebFluxSecurityConfiguration#WEB_FILTER_CHAIN_FILTER_ORDER} for more
*
* @param extensionGetter extension getter.
* @return additional web filter chain proxy.
*/
@Bean
@Order(-101)
AdditionalWebFilterChainProxy additionalWebFilterChainProxy(ExtensionGetter extensionGetter) {
return new AdditionalWebFilterChainProxy(extensionGetter);
}
}

View File

@ -30,9 +30,7 @@ import run.halo.app.core.extension.service.UserService;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.AnonymousUserConst;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.security.DefaultUserDetailService;
import run.halo.app.security.DynamicMatcherSecurityWebFilterChain;
import run.halo.app.security.authentication.SecurityConfigurer;
import run.halo.app.security.authentication.login.CryptoService;
import run.halo.app.security.authentication.login.PublicKeyRouteBuilder;
@ -60,7 +58,6 @@ public class WebServerSecurityConfig {
RoleService roleService,
ObjectProvider<SecurityConfigurer> securityConfigurers,
ServerSecurityContextRepository securityContextRepository,
ExtensionGetter extensionGetter,
ReactiveExtensionClient client,
PatJwkSupplier patJwkSupplier) {
@ -92,7 +89,7 @@ public class WebServerSecurityConfig {
// Integrate with other configurers separately
securityConfigurers.orderedStream()
.forEach(securityConfigurer -> securityConfigurer.configure(http));
return new DynamicMatcherSecurityWebFilterChain(extensionGetter, http.build());
return http.build();
}
@Bean

View File

@ -1,66 +0,0 @@
package run.halo.app.security;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.NonNull;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
/**
* A {@link SecurityWebFilterChain} that leverages a {@link ServerWebExchangeMatcher} to
* determine which {@link WebFilter} to execute.
*
* @author guqing
* @since 2.4.0
*/
public class DynamicMatcherSecurityWebFilterChain implements SecurityWebFilterChain {
private final SecurityWebFilterChain delegate;
private final ExtensionGetter extensionGetter;
public DynamicMatcherSecurityWebFilterChain(ExtensionGetter extensionGetter,
SecurityWebFilterChain delegate) {
this.delegate = delegate;
this.extensionGetter = extensionGetter;
}
@Override
public Mono<Boolean> matches(ServerWebExchange exchange) {
return delegate.matches(exchange);
}
@Override
public Flux<WebFilter> getWebFilters() {
return Flux.merge(delegate.getWebFilters(), getAdditionalFilters())
.sort(new AnnotationAwareOrderComparator());
}
private Flux<WebFilter> getAdditionalFilters() {
return extensionGetter.getEnabledExtensionByDefinition(AdditionalWebFilter.class)
.map(additionalWebFilter -> new OrderedWebFilter(additionalWebFilter,
additionalWebFilter.getOrder())
);
}
private record OrderedWebFilter(WebFilter webFilter, int order) implements WebFilter, Ordered {
@Override
@NonNull
public Mono<Void> filter(@NonNull ServerWebExchange exchange,
@NonNull WebFilterChain chain) {
return this.webFilter.filter(exchange, chain);
}
@Override
public int getOrder() {
return this.order;
}
}
}

View File

@ -0,0 +1,35 @@
package run.halo.app.webfilter;
import lombok.Setter;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.security.AdditionalWebFilter;
public class AdditionalWebFilterChainProxy implements WebFilter {
private final ExtensionGetter extensionGetter;
@Setter
private WebFilterChainProxy.WebFilterChainDecorator filterChainDecorator;
public AdditionalWebFilterChainProxy(ExtensionGetter extensionGetter) {
this.extensionGetter = extensionGetter;
this.filterChainDecorator = new WebFilterChainProxy.DefaultWebFilterChainDecorator();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return extensionGetter.getEnabledExtensionByDefinition(AdditionalWebFilter.class)
.sort(AnnotationAwareOrderComparator.INSTANCE)
.cast(WebFilter.class)
.collectList()
.map(filters -> filterChainDecorator.decorate(chain, filters))
.flatMap(decoratedChain -> decoratedChain.filter(exchange));
}
}