mirror of https://github.com/halo-dev/halo
feat: add LoginHandlerEnhancer for enhanced login processing (#6176)
#### What type of PR is this? /kind improvement /area core /milestone 2.17.x #### What this PR does / why we need it: 新增 LoginHandlerEnhancer 用于 Halo 扩展登录成功或失败后的处理逻辑如 RememberMe 和设备管理等 #### Does this PR introduce a user-facing change? ```release-note None ```pull/6211/head
parent
d92bb4398e
commit
967eaa21e1
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.17.0
|
||||
*/
|
||||
public interface LoginHandlerEnhancer {
|
||||
|
||||
/**
|
||||
* Invoked when login success.
|
||||
*
|
||||
* @param exchange The exchange.
|
||||
* @param successfulAuthentication The successful authentication.
|
||||
*/
|
||||
Mono<Void> onLoginSuccess(ServerWebExchange exchange, Authentication successfulAuthentication);
|
||||
|
||||
/**
|
||||
* Invoked when login fails.
|
||||
*
|
||||
* @param exchange The exchange.
|
||||
* @param exception the reason authentication failed
|
||||
*/
|
||||
Mono<Void> onLoginFailure(ServerWebExchange exchange, AuthenticationException exception);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<Void> onLoginSuccess(ServerWebExchange exchange,
|
||||
Authentication successfulAuthentication) {
|
||||
return rememberMeServices.loginSuccess(exchange, successfulAuthentication)
|
||||
.then(deviceService.loginSuccess(exchange, successfulAuthentication));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onLoginFailure(ServerWebExchange exchange,
|
||||
AuthenticationException exception) {
|
||||
return rememberMeServices.loginFail(exchange);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<Void> 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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue