diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/utilities/UserLogoutAction.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/utilities/UserLogoutAction.java new file mode 100644 index 000000000..fd4139cf7 --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/controller/request/cud/utilities/UserLogoutAction.java @@ -0,0 +1,28 @@ +package ru.spcex.clearing.backendapi.controller.request.cud.utilities; + + +import ru.spcex.clearing.backendapi.domain.actions.IAction; +import ru.spcex.clearing.platform.messaging.domain.ActionType; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.UserLogoutRequest; + +public class UserLogoutAction implements IAction { + private final UserLogoutRequest authRequest; + + public UserLogoutAction() { + this.authRequest = new UserLogoutRequest(); + } + + public UserLogoutRequest getAuthRequest() { + return authRequest; + } + + @Override + public UserLogoutRequest toRequest() { + return authRequest; + } + + @Override + public ActionType getActionType() { + return ActionType.SYSTEM; + } +} diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java index 3f6685e18..938127416 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java @@ -91,7 +91,7 @@ public class KeycloakRestTemplateAuthenticationProvider implements Authenticatio final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, skSession); KeycloakAuthenticationToken keycloakAuthenticationToken = new KeycloakAuthenticationToken(account, false, realmRoles); keycloakAuthenticationToken.setAuthenticated(true); - checkRightsAndCreate(principalName, roles); + log.debug("login successful: login {}, roles {}", principalName, String.join(";", roles)); return keycloakAuthenticationToken; } catch (VerificationException vex) { @@ -101,17 +101,6 @@ public class KeycloakRestTemplateAuthenticationProvider implements Authenticatio } } - private void checkRightsAndCreate(String username, Set roles) { - - log.debug("login successful: login {}, roles {}", username, String.join(";", roles)); - //todo remove hardcode - if (roles.contains("admin") || roles.contains("default-roles-master")) { - String clientIp = currentHttpRequest != null ? currentHttpRequest.getRemoteAddr() : null; - String serverIp = currentHttpRequest != null ? currentHttpRequest.getLocalAddr() : null; - userAutoCreation.sendUserInfo(username, roles, clientIp, serverIp); - } - } - @Override public boolean supports(Class type) { return UsernamePasswordAuthenticationToken.class.equals(type); diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java index fffe02360..3d5a25a21 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java @@ -5,7 +5,6 @@ import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticatio import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -14,19 +13,26 @@ import org.springframework.security.config.annotation.web.configurers.Expression import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import ru.spcex.clearing.backendapi.config.element.BackendApiSettings; +import ru.spcex.clearing.backendapi.security.handlers.AuthAuditSuccessLoginHandler; +import ru.spcex.clearing.backendapi.security.handlers.LogoutAuditSuccessHandler; @KeycloakConfiguration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { private final Boolean securityDisabled; + private final AuthAuditSuccessLoginHandler loginSuccessHandler; + private final LogoutAuditSuccessHandler logoutSuccessHandler; - public WebSecurityConfig(BackendApiSettings settings) { + public WebSecurityConfig(BackendApiSettings settings, + AuthAuditSuccessLoginHandler loginSuccessHandler, + LogoutAuditSuccessHandler logoutSuccessHandler) { this.securityDisabled = settings.getSecurity().getAuthorizationDisabled(); + this.loginSuccessHandler = loginSuccessHandler; + this.logoutSuccessHandler = logoutSuccessHandler; } @Override @@ -65,11 +71,11 @@ public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { anyReq.hasAnyRole("admin", "default-roles-master") .and() .formLogin() - .successHandler((req, res, auth) -> res.setStatus(HttpStatus.OK.value())) + .successHandler(loginSuccessHandler) .failureHandler(new SimpleUrlAuthenticationFailureHandler()) .and() .logout() - .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)) + .logoutSuccessHandler(logoutSuccessHandler) ; } } diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/AuthAuditSuccessLoginHandler.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/AuthAuditSuccessLoginHandler.java new file mode 100644 index 000000000..f466829e9 --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/AuthAuditSuccessLoginHandler.java @@ -0,0 +1,51 @@ +package ru.spcex.clearing.backendapi.security.handlers; + +import org.keycloak.KeycloakPrincipal; +import org.keycloak.adapters.spi.KeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Service; +import ru.spcex.clearing.backendapi.service.UserAuthProcessor; +import ru.spcex.platform.utils.log.ExceptionUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Set; + +@Service +public class AuthAuditSuccessLoginHandler implements AuthenticationSuccessHandler { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final UserAuthProcessor userAutoCreation; + + @Autowired + public AuthAuditSuccessLoginHandler(UserAuthProcessor userAutoCreation) { + this.userAutoCreation = userAutoCreation; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + try { + KeycloakAuthenticationToken auth = (KeycloakAuthenticationToken) authentication; + KeycloakAccount details = (KeycloakAccount) auth.getDetails(); + String username = ((KeycloakPrincipal) details.getPrincipal()) + .getKeycloakSecurityContext() + .getToken() + .getPreferredUsername(); + Set roles = details.getRoles(); + if (roles.contains("admin") || roles.contains("default-roles-master")) { + String clientIp = request != null ? request.getRemoteAddr() : null; + String serverIp = request != null ? request.getLocalAddr() : null; + userAutoCreation.sendAuthSuccessEvent(username, roles, clientIp, serverIp); + } + } catch (Throwable e) { + log.error(ExceptionUtils.getStackTrace(e)); + } + response.setStatus(HttpStatus.OK.value()); + } + +} diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/LogoutAuditSuccessHandler.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/LogoutAuditSuccessHandler.java new file mode 100644 index 000000000..8b749707a --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/handlers/LogoutAuditSuccessHandler.java @@ -0,0 +1,48 @@ +package ru.spcex.clearing.backendapi.security.handlers; + +import org.keycloak.KeycloakPrincipal; +import org.keycloak.adapters.spi.KeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; +import org.springframework.stereotype.Service; +import ru.spcex.clearing.backendapi.service.UserAuthProcessor; +import ru.spcex.platform.utils.log.ExceptionUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Service +public class LogoutAuditSuccessHandler extends HttpStatusReturningLogoutSuccessHandler { + private final UserAuthProcessor logoutEventSender; + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + public LogoutAuditSuccessHandler(UserAuthProcessor logoutEventSender) { + super(HttpStatus.OK); + this.logoutEventSender = logoutEventSender; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + try { + KeycloakAuthenticationToken auth = (KeycloakAuthenticationToken) authentication; + KeycloakAccount details = (KeycloakAccount) auth.getDetails(); + String username = ((KeycloakPrincipal) details.getPrincipal()) + .getKeycloakSecurityContext() + .getToken() + .getPreferredUsername(); + this.logoutEventSender.sendLogoutSuccessEvent(username); + } catch (Throwable e) { + log.error(ExceptionUtils.getStackTrace(e)); + } + super.onLogoutSuccess(request, response, authentication); + } + +} diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/service/UserAuthProcessor.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/service/UserAuthProcessor.java index 061b2101f..32615128f 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/service/UserAuthProcessor.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/service/UserAuthProcessor.java @@ -6,8 +6,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import ru.spcex.clearing.backendapi.controller.request.cud.utilities.UserAuthAction; +import ru.spcex.clearing.backendapi.controller.request.cud.utilities.UserLogoutAction; import ru.spcex.clearing.platform.messaging.domain.Consts; import ru.spcex.clearing.platform.messaging.domain.cud.utilities.UserAuthRequest; +import ru.spcex.clearing.platform.messaging.domain.cud.utilities.UserLogoutRequest; import ru.spcex.platform.utils.log.ExceptionUtils; import java.net.InetAddress; @@ -31,7 +33,7 @@ public class UserAuthProcessor { this.operator = operator; } - public void sendUserInfo(String identifier, Collection roles, String userIp, String serverIp) { + public void sendAuthSuccessEvent(String identifier, Collection roles, String userIp, String serverIp) { UserAuthAction authEvent = new UserAuthAction(); UserAuthRequest requestData = authEvent.getAuthRequest(); requestData.setUsername(identifier); @@ -46,6 +48,18 @@ public class UserAuthProcessor { } } + public void sendLogoutSuccessEvent(String identifier) { + UserLogoutAction logoutEvent = new UserLogoutAction(); + UserLogoutRequest requestData = logoutEvent.getAuthRequest(); + requestData.setUsername(identifier); + requestData.setTime(Instant.now()); + try { + operator.sendRequestToQueue(Consts.USER_LOGOUT_SUCCESS, logoutEvent); + } catch (Throwable e) { + log.error(ExceptionUtils.getStackTrace(e)); + } + } + private String getServerAddress() { if (serverAddress != null) { return serverAddress; diff --git a/clearing-parent/backend-api/src/main/resources/application.properties b/clearing-parent/backend-api/src/main/resources/application.properties index 721f33f19..8c83a2cef 100644 --- a/clearing-parent/backend-api/src/main/resources/application.properties +++ b/clearing-parent/backend-api/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.main.web-application-type=servlet backend-api.example-setting=test -backend-api.hazelcast.cluster-members=127.0.0.1 +backend-api.hazelcast.cluster-members=127.0.0.1:5701 backend-api.hazelcast.login=dev backend-api.hazelcast.password=dev-pass diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java index b2e32711d..cc94a5fd5 100644 --- a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/Consts.java @@ -24,4 +24,5 @@ public interface Consts { String DESTINATION_SDF02_NEW = "s-df-02-new"; String USER_AUTH_SUCCESS = "user-auth-success"; + String USER_LOGOUT_SUCCESS = "user-logout-success"; } diff --git a/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/UserLogoutRequest.java b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/UserLogoutRequest.java new file mode 100644 index 000000000..bf6c64b65 --- /dev/null +++ b/platform-parent/platform-messaging/src/main/java/ru/spcex/clearing/platform/messaging/domain/cud/utilities/UserLogoutRequest.java @@ -0,0 +1,34 @@ +package ru.spcex.clearing.platform.messaging.domain.cud.utilities; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.spcex.clearing.platform.messaging.domain.json.deserialize.InstantDeserializer; +import ru.spcex.clearing.platform.messaging.domain.json.serialize.InstantSerializer; + +import java.time.Instant; + +public class UserLogoutRequest { + @JsonProperty + private String username; + @JsonSerialize(using = InstantSerializer.class) + @JsonDeserialize(using = InstantDeserializer.class) + @JsonProperty + private Instant time; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Instant getTime() { + return time; + } + + public void setTime(Instant time) { + this.time = time; + } +}