diff --git a/api/src/main/java/run/halo/app/security/AdditionalWebFilter.java b/api/src/main/java/run/halo/app/security/AdditionalWebFilter.java index f20a39bb60..eacf3fbcc2 100644 --- a/api/src/main/java/run/halo/app/security/AdditionalWebFilter.java +++ b/api/src/main/java/run/halo/app/security/AdditionalWebFilter.java @@ -22,4 +22,14 @@ public interface AdditionalWebFilter extends WebFilter, ExtensionPoint, Ordered default int getOrder() { return Ordered.LOWEST_PRECEDENCE; } + + default Scope getScope() { + return Scope.PROTECTED_API; + } + + enum Scope { + PROTECTED_API, + PORTAL, + ALL + } } diff --git a/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java b/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java index 6d1a5d4ce4..544a1a5248 100644 --- a/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java +++ b/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java @@ -31,6 +31,7 @@ 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.AdditionalWebFilter; import run.halo.app.security.DefaultUserDetailService; import run.halo.app.security.DynamicMatcherSecurityWebFilterChain; import run.halo.app.security.authentication.SecurityConfigurer; @@ -92,14 +93,16 @@ SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http, // Integrate with other configurers separately securityConfigurers.orderedStream() .forEach(securityConfigurer -> securityConfigurer.configure(http)); - return new DynamicMatcherSecurityWebFilterChain(extensionGetter, http.build()); + return new DynamicMatcherSecurityWebFilterChain(extensionGetter, http.build(), + Set.of(AdditionalWebFilter.Scope.PROTECTED_API)); } @Bean @Order(Ordered.HIGHEST_PRECEDENCE + 1) SecurityWebFilterChain portalFilterChain(ServerHttpSecurity http, ServerSecurityContextRepository securityContextRepository, - HaloProperties haloProperties) { + HaloProperties haloProperties, + ExtensionGetter extensionGetter) { var pathMatcher = pathMatchers(HttpMethod.GET, "/**"); var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML); mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL)); @@ -126,7 +129,8 @@ SecurityWebFilterChain portalFilterChain(ServerHttpSecurity http, new HaloAnonymousAuthenticationWebFilter("portal", AnonymousUserConst.PRINCIPAL, AuthorityUtils.createAuthorityList(AnonymousUserConst.Role), securityContextRepository))); - return http.build(); + return new DynamicMatcherSecurityWebFilterChain(extensionGetter, http.build(), + Set.of(AdditionalWebFilter.Scope.PORTAL)); } @Bean diff --git a/application/src/main/java/run/halo/app/security/DynamicMatcherSecurityWebFilterChain.java b/application/src/main/java/run/halo/app/security/DynamicMatcherSecurityWebFilterChain.java index 45095f26f4..bb2cda18bb 100644 --- a/application/src/main/java/run/halo/app/security/DynamicMatcherSecurityWebFilterChain.java +++ b/application/src/main/java/run/halo/app/security/DynamicMatcherSecurityWebFilterChain.java @@ -1,10 +1,13 @@ package run.halo.app.security; +import java.util.Set; 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.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; @@ -25,10 +28,23 @@ public class DynamicMatcherSecurityWebFilterChain implements SecurityWebFilterCh private final ExtensionGetter extensionGetter; + private final Set matchScopes; + + /** + * Creates an aggregated {@link SecurityWebFilterChain} using the provided original and + * additional filters. + * + * @param matchScopes Only matched the given scopes will be added to the filter chain + */ public DynamicMatcherSecurityWebFilterChain(ExtensionGetter extensionGetter, - SecurityWebFilterChain delegate) { + SecurityWebFilterChain delegate, + Set matchScopes) { + Assert.isTrue(!CollectionUtils.isEmpty(matchScopes), "Match scopes must not be empty"); + Assert.notNull(extensionGetter, "Extension getter must not be null"); + Assert.notNull(delegate, "Delegate must not be null"); this.delegate = delegate; this.extensionGetter = extensionGetter; + this.matchScopes = matchScopes; } @Override @@ -44,6 +60,10 @@ public Flux getWebFilters() { private Flux getAdditionalFilters() { return extensionGetter.getEnabledExtensionByDefinition(AdditionalWebFilter.class) + .filter(additionalWebFilter -> { + var scope = additionalWebFilter.getScope(); + return scope == AdditionalWebFilter.Scope.ALL || matchScopes.contains(scope); + }) .map(additionalWebFilter -> new OrderedWebFilter(additionalWebFilter, additionalWebFilter.getOrder()) );