Provide extension points for authentication-related web filters (#5386)

#### What type of PR is this?

/kind feature
/area core
/area plugin
/milestone 2.13.x

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

See https://github.com/halo-dev/halo/issues/5379 for more.

This PR provides three extension points:

- FormLoginSecurityWebFilter
- AuthenticationSecurityWebFilter
- AnonymousAuthenticationSecurityWebFilter

which could be extended by plugins easily.

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

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

#### Special notes for your reviewer:

TBD.

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

```release-note
None
```
pull/5400/head
John Niang 2024-02-23 17:04:40 +08:00 committed by GitHub
parent 50fbe37be8
commit bbe79bac10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 204 additions and 0 deletions

View File

@ -0,0 +1,13 @@
package run.halo.app.security;
import org.pf4j.ExtensionPoint;
import org.springframework.web.server.WebFilter;
/**
* Security web filter for anonymous authentication.
*
* @author johnniang
*/
public interface AnonymousAuthenticationSecurityWebFilter extends WebFilter, ExtensionPoint {
}

View File

@ -0,0 +1,13 @@
package run.halo.app.security;
import org.pf4j.ExtensionPoint;
import org.springframework.web.server.WebFilter;
/**
* Security web filter for normal authentication.
*
* @author johnniang
*/
public interface AuthenticationSecurityWebFilter extends WebFilter, ExtensionPoint {
}

View File

@ -0,0 +1,13 @@
package run.halo.app.security;
import org.pf4j.ExtensionPoint;
import org.springframework.web.server.WebFilter;
/**
* Security web filter for form login.
*
* @author johnniang
*/
public interface FormLoginSecurityWebFilter extends WebFilter, ExtensionPoint {
}

View File

@ -14,6 +14,7 @@ import org.springframework.stereotype.Component;
* @since 2.0.0
*/
@Component
@Deprecated(forRemoval = true)
public class ExtensionComponentsFinder {
public static final String SYSTEM_PLUGIN_ID = "system";
private final PluginManager pluginManager;

View File

@ -1,5 +1,6 @@
package run.halo.app.plugin.extensionpoint;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@ -86,6 +87,15 @@ public class DefaultExtensionGetter implements ExtensionGetter {
});
}
@Override
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass) {
var extensions = new ArrayList<>(pluginManager.getExtensions(extensionPointClass));
applicationContext.getBeanProvider(extensionPointClass)
.orderedStream()
.forEach(extensions::add);
return Flux.fromIterable(extensions);
}
@NonNull
<T extends ExtensionPoint> List<T> getAllExtensions(Class<T> extensionPoint) {
Stream<T> pluginExtsStream = pluginManager.getExtensions(extensionPoint)

View File

@ -34,4 +34,13 @@ public interface ExtensionGetter {
* the {@link ExtensionPointDefinition}.
*/
<T extends ExtensionPoint> Flux<T> getEnabledExtensionByDefinition(Class<T> extensionPoint);
/**
* Get all extensions according to extension point class.
*
* @param extensionPointClass extension point class
* @param <T> type of extension point
* @return a bunch of extension points.
*/
<T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass);
}

View File

@ -0,0 +1,68 @@
package run.halo.app.security;
import static org.springframework.security.config.web.server.SecurityWebFiltersOrder.ANONYMOUS_AUTHENTICATION;
import static org.springframework.security.config.web.server.SecurityWebFiltersOrder.AUTHENTICATION;
import static org.springframework.security.config.web.server.SecurityWebFiltersOrder.FORM_LOGIN;
import lombok.Setter;
import org.pf4j.ExtensionPoint;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.stereotype.Component;
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.authentication.SecurityConfigurer;
@Component
public class SecurityWebFiltersConfigurer implements SecurityConfigurer {
private final ExtensionGetter extensionGetter;
public SecurityWebFiltersConfigurer(ExtensionGetter extensionGetter) {
this.extensionGetter = extensionGetter;
}
@Override
public void configure(ServerHttpSecurity http) {
http
.addFilterAt(
new SecurityWebFilterChainProxy(FormLoginSecurityWebFilter.class), FORM_LOGIN
)
.addFilterAt(
new SecurityWebFilterChainProxy(AuthenticationSecurityWebFilter.class),
AUTHENTICATION
)
.addFilterAt(
new SecurityWebFilterChainProxy(AnonymousAuthenticationSecurityWebFilter.class),
ANONYMOUS_AUTHENTICATION
);
}
public class SecurityWebFilterChainProxy implements WebFilter {
@Setter
private WebFilterChainProxy.WebFilterChainDecorator filterChainDecorator;
private final Class<? extends ExtensionPoint> extensionPointClass;
public SecurityWebFilterChainProxy(Class<? extends ExtensionPoint> extensionPointClass) {
this.extensionPointClass = extensionPointClass;
this.filterChainDecorator = new WebFilterChainProxy.DefaultWebFilterChainDecorator();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return extensionGetter.getExtensions(this.extensionPointClass)
.sort(AnnotationAwareOrderComparator.INSTANCE)
.cast(WebFilter.class)
.collectList()
.map(filters -> filterChainDecorator.decorate(chain, filters))
.flatMap(decoratedChain -> decoratedChain.filter(exchange));
}
}
}

View File

@ -0,0 +1,77 @@
# Halo 认证扩展点
此前Halo 提供了 AdditionalWebFilter 作为扩展点供插件扩展认证相关的功能。但是近期我们明确了 AdditionalWebFilter
的使用用途,故不再作为认证的扩展点。
目前Halo 提供了三种认证扩展点:表单登录认证、普通认证和匿名认证。
## 表单登录FormLogin
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.FormLoginSecurityWebFilter;
@Component
public class MyFormLoginSecurityWebFilter implements FormLoginSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
## 普通认证Authentication
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.AuthenticationSecurityWebFilter;
@Component
public class MyAuthenticationSecurityWebFilter implements AuthenticationSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
## 匿名认证Anonymous Authentication
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.AnonymousAuthenticationSecurityWebFilter;
@Component
public class MyAnonymousAuthenticationSecurityWebFilter
implements AnonymousAuthenticationSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
我们在实现扩展点的时候需要注意:如果当前请求不满足认证条件,请一定要调用 `chain.filter(exchange)`,给其他 filter 留下机会。
后续会根据需求实现其他认证相关的扩展点。