diff --git a/api/src/main/java/run/halo/app/security/LoginHandlerEnhancer.java b/api/src/main/java/run/halo/app/security/LoginHandlerEnhancer.java
new file mode 100644
index 000000000..33c9a0788
--- /dev/null
+++ b/api/src/main/java/run/halo/app/security/LoginHandlerEnhancer.java
@@ -0,0 +1,34 @@
+package run.halo.app.security;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ *
Halo uses this interface to enhance the processing of login success, such as device management
+ * and remember me, etc. The login method of the plugin extension needs to call this interface in
+ * the processing method of login success to ensure the normal operation of some enhanced
+ * functions.
+ *
+ * @author guqing
+ * @since 2.17.0
+ */
+public interface LoginHandlerEnhancer {
+
+ /**
+ * Invoked when login success.
+ *
+ * @param exchange The exchange.
+ * @param successfulAuthentication The successful authentication.
+ */
+ Mono onLoginSuccess(ServerWebExchange exchange, Authentication successfulAuthentication);
+
+ /**
+ * Invoked when login fails.
+ *
+ * @param exchange The exchange.
+ * @param exception the reason authentication failed
+ */
+ Mono onLoginFailure(ServerWebExchange exchange, AuthenticationException exception);
+}
diff --git a/application/src/main/java/run/halo/app/plugin/SharedApplicationContextFactory.java b/application/src/main/java/run/halo/app/plugin/SharedApplicationContextFactory.java
index 3f929ef21..b195aa4ec 100644
--- a/application/src/main/java/run/halo/app/plugin/SharedApplicationContextFactory.java
+++ b/application/src/main/java/run/halo/app/plugin/SharedApplicationContextFactory.java
@@ -14,6 +14,7 @@ import run.halo.app.infra.ExternalLinkProcessor;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.notification.NotificationCenter;
import run.halo.app.notification.NotificationReasonEmitter;
+import run.halo.app.security.LoginHandlerEnhancer;
/**
* Utility for creating shared application context.
@@ -59,6 +60,8 @@ public enum SharedApplicationContextFactory {
rootContext.getBean(PostContentService.class));
beanFactory.registerSingleton("cacheManager",
rootContext.getBean(CacheManager.class));
+ beanFactory.registerSingleton("loginHandlerEnhancer",
+ rootContext.getBean(LoginHandlerEnhancer.class));
// TODO add more shared instance here
sharedContext.refresh();
diff --git a/application/src/main/java/run/halo/app/security/LoginHandlerEnhancerImpl.java b/application/src/main/java/run/halo/app/security/LoginHandlerEnhancerImpl.java
new file mode 100644
index 000000000..bb72c7ca0
--- /dev/null
+++ b/application/src/main/java/run/halo/app/security/LoginHandlerEnhancerImpl.java
@@ -0,0 +1,39 @@
+package run.halo.app.security;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import run.halo.app.security.authentication.rememberme.RememberMeServices;
+import run.halo.app.security.device.DeviceService;
+
+/**
+ * A default implementation for {@link LoginHandlerEnhancer} to handle device management and
+ * remember me.
+ *
+ * @author guqing
+ * @since 2.17.0
+ */
+@Component
+@RequiredArgsConstructor
+public class LoginHandlerEnhancerImpl implements LoginHandlerEnhancer {
+
+ private final RememberMeServices rememberMeServices;
+
+ private final DeviceService deviceService;
+
+ @Override
+ public Mono onLoginSuccess(ServerWebExchange exchange,
+ Authentication successfulAuthentication) {
+ return rememberMeServices.loginSuccess(exchange, successfulAuthentication)
+ .then(deviceService.loginSuccess(exchange, successfulAuthentication));
+ }
+
+ @Override
+ public Mono onLoginFailure(ServerWebExchange exchange,
+ AuthenticationException exception) {
+ return rememberMeServices.loginFail(exchange);
+ }
+}
diff --git a/application/src/main/java/run/halo/app/security/authentication/login/LoginSecurityConfigurer.java b/application/src/main/java/run/halo/app/security/authentication/login/LoginSecurityConfigurer.java
index edc8db0b6..8fff49645 100644
--- a/application/src/main/java/run/halo/app/security/authentication/login/LoginSecurityConfigurer.java
+++ b/application/src/main/java/run/halo/app/security/authentication/login/LoginSecurityConfigurer.java
@@ -18,10 +18,9 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
+import run.halo.app.security.LoginHandlerEnhancer;
import run.halo.app.security.authentication.CryptoService;
import run.halo.app.security.authentication.SecurityConfigurer;
-import run.halo.app.security.authentication.rememberme.RememberMeServices;
-import run.halo.app.security.device.DeviceService;
@Component
public class LoginSecurityConfigurer implements SecurityConfigurer {
@@ -43,8 +42,7 @@ public class LoginSecurityConfigurer implements SecurityConfigurer {
private final MessageSource messageSource;
private final RateLimiterRegistry rateLimiterRegistry;
- private final RememberMeServices rememberMeServices;
- private final DeviceService deviceService;
+ private final LoginHandlerEnhancer loginHandlerEnhancer;
public LoginSecurityConfigurer(ObservationRegistry observationRegistry,
ReactiveUserDetailsService userDetailsService,
@@ -52,8 +50,7 @@ public class LoginSecurityConfigurer implements SecurityConfigurer {
ServerSecurityContextRepository securityContextRepository, CryptoService cryptoService,
ExtensionGetter extensionGetter, ServerResponse.Context context,
MessageSource messageSource, RateLimiterRegistry rateLimiterRegistry,
- RememberMeServices rememberMeServices,
- DeviceService deviceService) {
+ LoginHandlerEnhancer loginHandlerEnhancer) {
this.observationRegistry = observationRegistry;
this.userDetailsService = userDetailsService;
this.passwordService = passwordService;
@@ -64,8 +61,7 @@ public class LoginSecurityConfigurer implements SecurityConfigurer {
this.context = context;
this.messageSource = messageSource;
this.rateLimiterRegistry = rateLimiterRegistry;
- this.rememberMeServices = rememberMeServices;
- this.deviceService = deviceService;
+ this.loginHandlerEnhancer = loginHandlerEnhancer;
}
@Override
@@ -73,7 +69,7 @@ public class LoginSecurityConfigurer implements SecurityConfigurer {
var filter = new AuthenticationWebFilter(authenticationManager());
var requiresMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login");
var handler =
- new UsernamePasswordHandler(context, messageSource, rememberMeServices, deviceService);
+ new UsernamePasswordHandler(context, messageSource, loginHandlerEnhancer);
var authConverter = new LoginAuthenticationConverter(cryptoService, rateLimiterRegistry);
filter.setRequiresAuthenticationMatcher(requiresMatcher);
filter.setAuthenticationFailureHandler(handler);
diff --git a/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordHandler.java b/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordHandler.java
index f36a7097b..33ce5b04d 100644
--- a/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordHandler.java
+++ b/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordHandler.java
@@ -20,9 +20,8 @@ import org.springframework.web.ErrorResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
-import run.halo.app.security.authentication.rememberme.RememberMeServices;
+import run.halo.app.security.LoginHandlerEnhancer;
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
-import run.halo.app.security.device.DeviceService;
@Slf4j
public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandler,
@@ -32,9 +31,7 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
private final MessageSource messageSource;
- private final RememberMeServices rememberMeServices;
-
- private final DeviceService deviceService;
+ private final LoginHandlerEnhancer loginHandlerEnhancer;
private final ServerAuthenticationFailureHandler defaultFailureHandler =
new RedirectServerAuthenticationFailureHandler("/console?error#/login");
@@ -43,18 +40,17 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
new RedirectServerAuthenticationSuccessHandler("/console/");
public UsernamePasswordHandler(ServerResponse.Context context, MessageSource messageSource,
- RememberMeServices rememberMeServices, DeviceService deviceService) {
+ LoginHandlerEnhancer loginHandlerEnhancer) {
this.context = context;
this.messageSource = messageSource;
- this.rememberMeServices = rememberMeServices;
- this.deviceService = deviceService;
+ this.loginHandlerEnhancer = loginHandlerEnhancer;
}
@Override
public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange,
AuthenticationException exception) {
var exchange = webFilterExchange.getExchange();
- return rememberMeServices.loginFail(exchange)
+ return loginHandlerEnhancer.onLoginFailure(exchange, exception)
.then(ignoringMediaTypeAll(APPLICATION_JSON)
.matches(exchange)
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
@@ -71,8 +67,8 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
Authentication authentication) {
if (authentication instanceof TwoFactorAuthentication) {
// continue filtering for authorization
- return rememberMeServices.loginSuccess(webFilterExchange.getExchange(), authentication)
- .then(deviceService.loginSuccess(webFilterExchange.getExchange(), authentication))
+ return loginHandlerEnhancer.onLoginSuccess(webFilterExchange.getExchange(),
+ authentication)
.then(webFilterExchange.getChain().filter(webFilterExchange.getExchange()));
}
@@ -89,8 +85,7 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
};
var exchange = webFilterExchange.getExchange();
- return rememberMeServices.loginSuccess(exchange, authentication)
- .then(deviceService.loginSuccess(exchange, authentication))
+ return loginHandlerEnhancer.onLoginSuccess(webFilterExchange.getExchange(), authentication)
.then(xhrMatcher.matches(exchange)
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
.switchIfEmpty(Mono.defer(
diff --git a/application/src/main/java/run/halo/app/security/authentication/twofactor/TwoFactorAuthSecurityConfigurer.java b/application/src/main/java/run/halo/app/security/authentication/twofactor/TwoFactorAuthSecurityConfigurer.java
index b53d289f9..d8dd6a770 100644
--- a/application/src/main/java/run/halo/app/security/authentication/twofactor/TwoFactorAuthSecurityConfigurer.java
+++ b/application/src/main/java/run/halo/app/security/authentication/twofactor/TwoFactorAuthSecurityConfigurer.java
@@ -6,11 +6,10 @@ import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse;
+import run.halo.app.security.LoginHandlerEnhancer;
import run.halo.app.security.authentication.SecurityConfigurer;
-import run.halo.app.security.authentication.rememberme.RememberMeServices;
import run.halo.app.security.authentication.twofactor.totp.TotpAuthService;
import run.halo.app.security.authentication.twofactor.totp.TotpAuthenticationFilter;
-import run.halo.app.security.device.DeviceService;
@Component
public class TwoFactorAuthSecurityConfigurer implements SecurityConfigurer {
@@ -23,30 +22,26 @@ public class TwoFactorAuthSecurityConfigurer implements SecurityConfigurer {
private final MessageSource messageSource;
- private final RememberMeServices rememberMeServices;
-
- private final DeviceService deviceService;
+ private final LoginHandlerEnhancer loginHandlerEnhancer;
public TwoFactorAuthSecurityConfigurer(
ServerSecurityContextRepository securityContextRepository,
TotpAuthService totpAuthService,
ServerResponse.Context context,
MessageSource messageSource,
- RememberMeServices rememberMeServices,
- DeviceService deviceService
+ LoginHandlerEnhancer loginHandlerEnhancer
) {
this.securityContextRepository = securityContextRepository;
this.totpAuthService = totpAuthService;
this.context = context;
this.messageSource = messageSource;
- this.rememberMeServices = rememberMeServices;
- this.deviceService = deviceService;
+ this.loginHandlerEnhancer = loginHandlerEnhancer;
}
@Override
public void configure(ServerHttpSecurity http) {
var filter = new TotpAuthenticationFilter(securityContextRepository, totpAuthService,
- context, messageSource, rememberMeServices, deviceService);
+ context, messageSource, loginHandlerEnhancer);
http.addFilterAfter(filter, SecurityWebFiltersOrder.AUTHENTICATION);
}
}
diff --git a/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java b/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java
index 726fdcbed..b2140007d 100644
--- a/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java
+++ b/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java
@@ -19,10 +19,9 @@ import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import run.halo.app.security.HaloUserDetails;
+import run.halo.app.security.LoginHandlerEnhancer;
import run.halo.app.security.authentication.login.UsernamePasswordHandler;
-import run.halo.app.security.authentication.rememberme.RememberMeServices;
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
-import run.halo.app.security.device.DeviceService;
@Slf4j
public class TotpAuthenticationFilter extends AuthenticationWebFilter {
@@ -32,8 +31,7 @@ public class TotpAuthenticationFilter extends AuthenticationWebFilter {
TotpAuthService totpAuthService,
ServerResponse.Context context,
MessageSource messageSource,
- RememberMeServices rememberMeServices,
- DeviceService deviceService
+ LoginHandlerEnhancer loginHandlerEnhancer
) {
super(new TwoFactorAuthManager(totpAuthService));
@@ -41,8 +39,7 @@ public class TotpAuthenticationFilter extends AuthenticationWebFilter {
setRequiresAuthenticationMatcher(pathMatchers(HttpMethod.POST, "/login/2fa/totp"));
setServerAuthenticationConverter(new TotpCodeAuthenticationConverter());
- var handler =
- new UsernamePasswordHandler(context, messageSource, rememberMeServices, deviceService);
+ var handler = new UsernamePasswordHandler(context, messageSource, loginHandlerEnhancer);
setAuthenticationSuccessHandler(handler);
setAuthenticationFailureHandler(handler);
}