Add shared events for listening user login and logout in plugins (#7440)

#### What type of PR is this?
/kind improvement

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

This PR adds UserLoginEvent and UserLogoutEvent which are shared to plugins.

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

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

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

```release-note
添加用户登录/登出事件
```
pull/7470/head
柳意梧情 2025-05-23 16:54:12 +08:00 committed by GitHub
parent a1c48b4943
commit 5c27a0484b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 1 deletions

View File

@ -0,0 +1,23 @@
package run.halo.app.event.user;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import run.halo.app.core.extension.User;
import run.halo.app.plugin.SharedEvent;
/**
* User login event.
*
* @author lywq
**/
@SharedEvent
public class UserLoginEvent extends ApplicationEvent {
@Getter
private final User user;
public UserLoginEvent(Object source, User user) {
super(source);
this.user = user;
}
}

View File

@ -0,0 +1,23 @@
package run.halo.app.event.user;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import run.halo.app.core.extension.User;
import run.halo.app.plugin.SharedEvent;
/**
* User logout event.
*
* @author lywq
**/
@SharedEvent
public class UserLogoutEvent extends ApplicationEvent {
@Getter
private final User user;
public UserLogoutEvent(Object source, User user) {
super(source);
this.user = user;
}
}

View File

@ -0,0 +1,38 @@
package run.halo.app.core.user.service;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.halo.app.event.user.UserLoginEvent;
import run.halo.app.event.user.UserLogoutEvent;
/**
* User login or logout processing service.
*
* @author lywq
**/
@Component
@RequiredArgsConstructor
public class UserLoginOrLogoutProcessing {
private final UserService userService;
private final ApplicationEventPublisher eventPublisher;
public Mono<Void> loginProcessing(String username) {
return userService.getUser(username)
.doOnNext(user -> {
eventPublisher.publishEvent(new UserLoginEvent(this, user));
})
.then();
}
public Mono<Void> logoutProcessing(String username) {
return userService.getUser(username)
.doOnNext(user -> {
eventPublisher.publishEvent(new UserLogoutEvent(this, user));
})
.then();
}
}

View File

@ -6,6 +6,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.core.user.service.UserLoginOrLogoutProcessing;
import run.halo.app.security.authentication.oauth2.OAuth2LoginHandlerEnhancer; import run.halo.app.security.authentication.oauth2.OAuth2LoginHandlerEnhancer;
import run.halo.app.security.authentication.rememberme.RememberMeRequestCache; import run.halo.app.security.authentication.rememberme.RememberMeRequestCache;
import run.halo.app.security.authentication.rememberme.RememberMeServices; import run.halo.app.security.authentication.rememberme.RememberMeServices;
@ -32,6 +33,8 @@ public class LoginHandlerEnhancerImpl implements LoginHandlerEnhancer {
private final OAuth2LoginHandlerEnhancer oauth2LoginHandlerEnhancer; private final OAuth2LoginHandlerEnhancer oauth2LoginHandlerEnhancer;
private final UserLoginOrLogoutProcessing userLoginOrLogoutProcessing;
@Override @Override
public Mono<Void> onLoginSuccess(ServerWebExchange exchange, public Mono<Void> onLoginSuccess(ServerWebExchange exchange,
Authentication successfulAuthentication) { Authentication successfulAuthentication) {
@ -39,7 +42,8 @@ public class LoginHandlerEnhancerImpl implements LoginHandlerEnhancer {
rememberMeServices.loginSuccess(exchange, successfulAuthentication), rememberMeServices.loginSuccess(exchange, successfulAuthentication),
deviceService.loginSuccess(exchange, successfulAuthentication), deviceService.loginSuccess(exchange, successfulAuthentication),
rememberMeRequestCache.removeRememberMe(exchange), rememberMeRequestCache.removeRememberMe(exchange),
oauth2LoginHandlerEnhancer.loginSuccess(exchange, successfulAuthentication) oauth2LoginHandlerEnhancer.loginSuccess(exchange, successfulAuthentication),
userLoginOrLogoutProcessing.loginProcessing(successfulAuthentication.getName())
); );
} }

View File

@ -30,6 +30,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.core.user.service.UserLoginOrLogoutProcessing;
import run.halo.app.core.user.service.UserService; import run.halo.app.core.user.service.UserService;
import run.halo.app.infra.actuator.GlobalInfoService; import run.halo.app.infra.actuator.GlobalInfoService;
import run.halo.app.security.authentication.SecurityConfigurer; import run.halo.app.security.authentication.SecurityConfigurer;
@ -45,6 +46,8 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final UserLoginOrLogoutProcessing userLoginOrLogoutProcessing;
private final ServerRequestCache serverRequestCache = new HaloServerRequestCache(); private final ServerRequestCache serverRequestCache = new HaloServerRequestCache();
@Override @Override
@ -108,6 +111,7 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
Authentication authentication) { Authentication authentication) {
return logoutHandler.logout(exchange, authentication) return logoutHandler.logout(exchange, authentication)
.then(rememberMeServices.loginFail(exchange.getExchange())) .then(rememberMeServices.loginFail(exchange.getExchange()))
.then(userLoginOrLogoutProcessing.logoutProcessing(authentication.getName()))
.then(ignoringMediaTypeAll(MediaType.APPLICATION_JSON) .then(ignoringMediaTypeAll(MediaType.APPLICATION_JSON)
.matches(exchange.getExchange()) .matches(exchange.getExchange())
.filter(ServerWebExchangeMatcher.MatchResult::isMatch) .filter(ServerWebExchangeMatcher.MatchResult::isMatch)