mirror of https://github.com/halo-dev/halo
Support extending username password authentication (#4265)
#### What type of PR is this? /kind feature /area core /area plugin #### What this PR does / why we need it: Plugin developers are able to define own UsernamePasswordAuthenticationManager to take charge of username password authentication. 1. If the manager fails to handle, the default authentication manager will be used. 2. If the manager returns `Mono.empty()`, the default authentication manager will be used. For example: ```java @Component public class LdapAuthenticationManager extends UserDetailsRepositoryReactiveAuthenticationManager implements UsernamePasswordAuthenticationManager { public LdapAuthenticationManager(ReactiveUserDetailsService userDetailsService) { super(userDetailsService); } @Override protected Mono<UserDetails> retrieveUser(String username) { return super.retrieveUser(username); } } ``` #### Which issue(s) this PR fixes: See https://github.com/halo-dev/halo/issues/4207#issuecomment-1643042348 for more. #### Does this PR introduce a user-facing change? ```release-note 提供用户名密码认证扩展 ```pull/4290/head
parent
0d19ccdb8a
commit
4505fcfd16
|
@ -0,0 +1,18 @@
|
|||
package run.halo.app.security.authentication.login;
|
||||
|
||||
import org.pf4j.ExtensionPoint;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
|
||||
/**
|
||||
* An extension point for username password authentication.
|
||||
* Any non-authentication exception occurs, the default authentication will be used.
|
||||
* If you want to skip authentication, please return Mono.empty() directly, the default
|
||||
* authentication will be used.
|
||||
*
|
||||
* @author johnniang
|
||||
* @since 2.8
|
||||
*/
|
||||
public interface UsernamePasswordAuthenticationManager
|
||||
extends ReactiveAuthenticationManager, ExtensionPoint {
|
||||
|
||||
}
|
|
@ -40,6 +40,7 @@ import org.springframework.web.server.WebFilterChain;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.infra.exception.RateLimitExceededException;
|
||||
import run.halo.app.infra.utils.IpAddressUtils;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||
import run.halo.app.security.AdditionalWebFilter;
|
||||
|
||||
/**
|
||||
|
@ -71,11 +72,14 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
|
|||
private final RateLimiterRegistry rateLimiterRegistry;
|
||||
private final MessageSource messageSource;
|
||||
|
||||
private final ExtensionGetter extensionGetter;
|
||||
|
||||
public UsernamePasswordAuthenticator(ServerResponse.Context context,
|
||||
ObservationRegistry observationRegistry, ReactiveUserDetailsService userDetailsService,
|
||||
ReactiveUserDetailsPasswordService passwordService, PasswordEncoder passwordEncoder,
|
||||
ServerSecurityContextRepository securityContextRepository, CryptoService cryptoService,
|
||||
RateLimiterRegistry rateLimiterRegistry, MessageSource messageSource) {
|
||||
RateLimiterRegistry rateLimiterRegistry, MessageSource messageSource,
|
||||
ExtensionGetter extensionGetter) {
|
||||
this.context = context;
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.userDetailsService = userDetailsService;
|
||||
|
@ -85,6 +89,7 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
|
|||
this.cryptoService = cryptoService;
|
||||
this.rateLimiterRegistry = rateLimiterRegistry;
|
||||
this.messageSource = messageSource;
|
||||
this.extensionGetter = extensionGetter;
|
||||
|
||||
this.authenticationWebFilter =
|
||||
new UsernamePasswordAuthenticationWebFilter(authenticationManager());
|
||||
|
@ -113,12 +118,17 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
|
|||
}
|
||||
|
||||
ReactiveAuthenticationManager authenticationManager() {
|
||||
var manager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
|
||||
manager.setPasswordEncoder(passwordEncoder);
|
||||
manager.setUserDetailsPasswordService(passwordService);
|
||||
var manager = new UsernamePasswordDelegatingAuthenticationManager(extensionGetter,
|
||||
defaultAuthenticationManager());
|
||||
return new ObservationReactiveAuthenticationManager(observationRegistry, manager);
|
||||
}
|
||||
|
||||
ReactiveAuthenticationManager defaultAuthenticationManager() {
|
||||
var manager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
|
||||
manager.setPasswordEncoder(passwordEncoder);
|
||||
manager.setUserDetailsPasswordService(passwordService);
|
||||
return manager;
|
||||
}
|
||||
|
||||
private <T> RateLimiterOperator<T> createIPBasedRateLimiter(ServerWebExchange exchange) {
|
||||
var clientIp = IpAddressUtils.getClientIp(exchange.getRequest());
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package run.halo.app.security.authentication.login;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||
|
||||
@Slf4j
|
||||
public class UsernamePasswordDelegatingAuthenticationManager
|
||||
implements ReactiveAuthenticationManager {
|
||||
|
||||
private final ExtensionGetter extensionGetter;
|
||||
|
||||
private final ReactiveAuthenticationManager defaultAuthenticationManager;
|
||||
|
||||
public UsernamePasswordDelegatingAuthenticationManager(ExtensionGetter extensionGetter,
|
||||
ReactiveAuthenticationManager defaultAuthenticationManager) {
|
||||
this.extensionGetter = extensionGetter;
|
||||
this.defaultAuthenticationManager = defaultAuthenticationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
return extensionGetter
|
||||
.getEnabledExtensionByDefinition(UsernamePasswordAuthenticationManager.class)
|
||||
.next()
|
||||
.flatMap(authenticationManager -> authenticationManager.authenticate(authentication)
|
||||
.doOnError(t -> log.error(
|
||||
"failed to authenticate with {}, fallback to default username password "
|
||||
+ "authentication.", authenticationManager.getClass(), t)
|
||||
)
|
||||
.onErrorResume(
|
||||
t -> !(t instanceof AuthenticationException),
|
||||
t -> Mono.empty()
|
||||
)
|
||||
)
|
||||
.switchIfEmpty(
|
||||
Mono.defer(() -> defaultAuthenticationManager.authenticate(authentication))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -41,3 +41,14 @@ spec:
|
|||
displayName: CommentWidget
|
||||
type: SINGLETON
|
||||
description: "Provides an extension point for the comment widget on the theme-side."
|
||||
|
||||
---
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: ExtensionPointDefinition
|
||||
metadata:
|
||||
name: username-password-authentication-manager
|
||||
spec:
|
||||
className: run.halo.app.security.authentication.login.UsernamePasswordAuthenticationManager
|
||||
displayName: Username password authentication manager
|
||||
type: SINGLETON
|
||||
description: "Provides a way to extend the username password authentication."
|
||||
|
|
Loading…
Reference in New Issue