Move username-password authentication into security filter chain (#5348)

#### What type of PR is this?

/kind cleanup
/area core
/milestone 2.13.x

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

UsernamePasswordAuthenticator is a normal webfilter instead of authentication webfilter in security filter chain. There does not guarentee expected results due to different in execution order. So this PR changes UsernamePasswordAuthenticator to AuthenticationWebFilter for managing the filter by security filter chain.

By the way, these changes will not affect any plugins.

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

```release-note
None
```
pull/5369/head^2
John Niang 2024-02-18 16:46:15 +08:00 committed by GitHub
parent 94a51aba4f
commit 8156d9da8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 19 additions and 39 deletions

View File

@ -2,14 +2,13 @@ package run.halo.app.security.authentication.login;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry; import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager; import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -18,21 +17,11 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.extensionpoint.ExtensionGetter; import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.security.AdditionalWebFilter; import run.halo.app.security.authentication.SecurityConfigurer;
/**
* Authentication filter for username and password.
*
* @author guqing
* @since 2.4.0
*/
@Slf4j
@Component @Component
public class UsernamePasswordAuthenticator implements AdditionalWebFilter { public class LoginSecurityConfigurer implements SecurityConfigurer {
private final ObservationRegistry observationRegistry; private final ObservationRegistry observationRegistry;
@ -46,16 +35,17 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
private final CryptoService cryptoService; private final CryptoService cryptoService;
private final AuthenticationWebFilter authenticationWebFilter;
private final ExtensionGetter extensionGetter; private final ExtensionGetter extensionGetter;
private final ServerResponse.Context context;
private final MessageSource messageSource;
private final RateLimiterRegistry rateLimiterRegistry;
public UsernamePasswordAuthenticator(ServerResponse.Context context, public LoginSecurityConfigurer(ObservationRegistry observationRegistry,
ObservationRegistry observationRegistry, ReactiveUserDetailsService userDetailsService, ReactiveUserDetailsService userDetailsService,
ReactiveUserDetailsPasswordService passwordService, PasswordEncoder passwordEncoder, ReactiveUserDetailsPasswordService passwordService, PasswordEncoder passwordEncoder,
ServerSecurityContextRepository securityContextRepository, CryptoService cryptoService, ServerSecurityContextRepository securityContextRepository, CryptoService cryptoService,
RateLimiterRegistry rateLimiterRegistry, MessageSource messageSource, ExtensionGetter extensionGetter, ServerResponse.Context context,
ExtensionGetter extensionGetter) { MessageSource messageSource, RateLimiterRegistry rateLimiterRegistry) {
this.observationRegistry = observationRegistry; this.observationRegistry = observationRegistry;
this.userDetailsService = userDetailsService; this.userDetailsService = userDetailsService;
this.passwordService = passwordService; this.passwordService = passwordService;
@ -63,27 +53,14 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
this.securityContextRepository = securityContextRepository; this.securityContextRepository = securityContextRepository;
this.cryptoService = cryptoService; this.cryptoService = cryptoService;
this.extensionGetter = extensionGetter; this.extensionGetter = extensionGetter;
this.context = context;
this.authenticationWebFilter = new AuthenticationWebFilter(authenticationManager()); this.messageSource = messageSource;
configureAuthenticationWebFilter(this.authenticationWebFilter, context, messageSource, this.rateLimiterRegistry = rateLimiterRegistry;
rateLimiterRegistry);
} }
@Override @Override
@NonNull public void configure(ServerHttpSecurity http) {
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) { var filter = new AuthenticationWebFilter(authenticationManager());
return authenticationWebFilter.filter(exchange, chain);
}
@Override
public int getOrder() {
return SecurityWebFiltersOrder.FORM_LOGIN.getOrder();
}
void configureAuthenticationWebFilter(AuthenticationWebFilter filter,
ServerResponse.Context context,
MessageSource messageSource,
RateLimiterRegistry rateLimiterRegistry) {
var requiresMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login"); var requiresMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login");
var handler = new UsernamePasswordHandler(context, messageSource); var handler = new UsernamePasswordHandler(context, messageSource);
var authConverter = new LoginAuthenticationConverter(cryptoService, rateLimiterRegistry); var authConverter = new LoginAuthenticationConverter(cryptoService, rateLimiterRegistry);
@ -92,6 +69,8 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
filter.setAuthenticationSuccessHandler(handler); filter.setAuthenticationSuccessHandler(handler);
filter.setServerAuthenticationConverter(authConverter); filter.setServerAuthenticationConverter(authConverter);
filter.setSecurityContextRepository(securityContextRepository); filter.setSecurityContextRepository(securityContextRepository);
http.addFilterAt(filter, SecurityWebFiltersOrder.FORM_LOGIN);
} }
ReactiveAuthenticationManager authenticationManager() { ReactiveAuthenticationManager authenticationManager() {
@ -106,5 +85,4 @@ public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
manager.setUserDetailsPasswordService(passwordService); manager.setUserDetailsPasswordService(passwordService);
return manager; return manager;
} }
} }

View File

@ -1,9 +1,11 @@
# TODO Remove the username-password-authenticator in the future.
apiVersion: plugin.halo.run/v1alpha1 apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition kind: ExtensionDefinition
metadata: metadata:
name: username-password-authenticator name: username-password-authenticator
labels: labels:
auth.halo.run/extension-point-name: "additional-webfilter" auth.halo.run/extension-point-name: "additional-webfilter"
deletionTimestamp: 2024-02-18T08:27:41.257531Z
spec: spec:
className: run.halo.app.security.authentication.login.UsernamePasswordAuthenticator className: run.halo.app.security.authentication.login.UsernamePasswordAuthenticator
extensionPointName: additional-webfilter extensionPointName: additional-webfilter