From 056ffef87c56dca270cec215e1dd0246ecba434f Mon Sep 17 00:00:00 2001 From: Anton Romanov Date: Fri, 14 Feb 2025 19:41:43 +0400 Subject: [PATCH] #11 -- Add user services --- .../ulstu/fc/user/UserNotFoundException.java | 7 ++ .../java/ru/ulstu/fc/user/UserRepository.java | 15 +++ .../ru/ulstu/fc/user/UserRoleRepository.java | 7 ++ .../java/ru/ulstu/fc/user/UserService.java | 97 +++++++++++++++++++ .../fc/user/UserSessionLoginHandler.java | 44 +++++++++ .../fc/user/UserSessionLogoutHandler.java | 48 +++++++++ .../ulstu/fc/user/UserSessionRepository.java | 13 +++ .../ru/ulstu/fc/user/UserSessionService.java | 40 ++++++++ src/main/java/ru/ulstu/fc/user/UserUtils.java | 24 +++++ .../ulstu/fc/{core => user}/model/User.java | 3 +- .../fc/{core => user}/model/UserRole.java | 2 +- .../model/UserRoleConstants.java | 2 +- .../fc/{core => user}/model/UserSession.java | 3 +- 13 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/ulstu/fc/user/UserNotFoundException.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserRepository.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserRoleRepository.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserService.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserSessionLoginHandler.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserSessionLogoutHandler.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserSessionRepository.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserSessionService.java create mode 100644 src/main/java/ru/ulstu/fc/user/UserUtils.java rename src/main/java/ru/ulstu/fc/{core => user}/model/User.java (96%) rename src/main/java/ru/ulstu/fc/{core => user}/model/UserRole.java (97%) rename src/main/java/ru/ulstu/fc/{core => user}/model/UserRoleConstants.java (81%) rename src/main/java/ru/ulstu/fc/{core => user}/model/UserSession.java (96%) diff --git a/src/main/java/ru/ulstu/fc/user/UserNotFoundException.java b/src/main/java/ru/ulstu/fc/user/UserNotFoundException.java new file mode 100644 index 0000000..49af789 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.fc.user; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/fc/user/UserRepository.java b/src/main/java/ru/ulstu/fc/user/UserRepository.java new file mode 100644 index 0000000..8610464 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserRepository.java @@ -0,0 +1,15 @@ +package ru.ulstu.fc.user; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.fc.user.model.User; + +public interface UserRepository extends JpaRepository { + User findOneByLoginIgnoreCase(String login); + + @EntityGraph(attributePaths = "roles") + User findOneWithRolesById(int id); + + @EntityGraph(attributePaths = "roles") + User findOneWithRolesByLogin(String login); +} diff --git a/src/main/java/ru/ulstu/fc/user/UserRoleRepository.java b/src/main/java/ru/ulstu/fc/user/UserRoleRepository.java new file mode 100644 index 0000000..5aec61f --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserRoleRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.fc.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.fc.user.model.UserRole; + +public interface UserRoleRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/fc/user/UserService.java b/src/main/java/ru/ulstu/fc/user/UserService.java new file mode 100644 index 0000000..517c4f0 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserService.java @@ -0,0 +1,97 @@ +package ru.ulstu.fc.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.fc.user.model.User; +import ru.ulstu.fc.user.model.UserRole; +import ru.ulstu.fc.user.model.UserRoleConstants; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@Transactional +public class UserService implements UserDetailsService { + private final Logger log = LoggerFactory.getLogger(UserService.class); + private final PasswordEncoder passwordEncoder; + private final UserRepository userRepository; + private final UserRoleRepository userRoleRepository; + @Value("${admin-password}") + private String adminPassword; + + public UserService(PasswordEncoder passwordEncoder, + UserRepository userRepository, + UserRoleRepository userRoleRepository) { + this.passwordEncoder = passwordEncoder; + this.userRepository = userRepository; + this.userRoleRepository = userRoleRepository; + } + + public User getUserByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + + @Override + public UserDetails loadUserByUsername(String username) { + final User user = userRepository.findOneByLoginIgnoreCase(username); + if (user == null) { + throw new UserNotFoundException(username); + } + return new org.springframework.security.core.userdetails.User(user.getLogin(), + user.getPassword(), + Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream() + .map(role -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toList())); + } + + public User createUser(User user) { + if (getUserByLogin(user.getLogin()) != null) { + throw new RuntimeException(user.getLogin()); + } + User dbUser = (user.getId() == null) + ? user + : getUserById(user.getId()); + //user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); + dbUser.setPassword(passwordEncoder.encode(user.getPassword())); + dbUser.setLogin(user.getLogin()); + dbUser = userRepository.save(dbUser); + log.debug("Created Information for User: {}", dbUser.getLogin()); + return dbUser; + } + + public User getUserById(Integer id) { + return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found by id")); + } + + private void createDefaultUser(String login, String userRole) { + if (getUserByLogin(login) == null) { + UserRole role = userRoleRepository.save(new UserRole(userRole.toString())); + createUser(new User(login, login.equals("admin") ? adminPassword : login, Set.of(role))); + } + } + + public void initDefaultAdmin() { + createDefaultUser("admin", UserRoleConstants.ADMIN); + } + + public void initDefaultAspirant() { + createDefaultUser("aspirant", UserRoleConstants.ASPIRANT); + } + + public void initDefaultManager() { + createDefaultUser("manager", UserRoleConstants.MANAGER); + } + + public void initDefaultHead() { + createDefaultUser("head", UserRoleConstants.HEAD); + } +} diff --git a/src/main/java/ru/ulstu/fc/user/UserSessionLoginHandler.java b/src/main/java/ru/ulstu/fc/user/UserSessionLoginHandler.java new file mode 100644 index 0000000..ded0a1b --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserSessionLoginHandler.java @@ -0,0 +1,44 @@ +package ru.ulstu.fc.user; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import ru.ulstu.fc.config.Constants; + +import java.io.IOException; + +@Component +public class UserSessionLoginHandler extends SavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private final Logger log = LoggerFactory.getLogger(UserSessionLoginHandler.class); + private final UserSessionService userSessionService; + + public UserSessionLoginHandler(UserSessionService userSessionService) { + super(); + this.userSessionService = userSessionService; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + super.onAuthenticationSuccess(request, response, authentication); + final String login = authentication.getName(); + final String ipAddress = IpAddressResolver.getRemoteAddr(request); + final String host = request.getRemoteHost(); + log.debug("Authentication Success for {}@{} ({})", login, ipAddress, host); + HttpSession session = request.getSession(false); + if (session != null) { + final String sessionId = session.getId(); + userSessionService.createUserSession(sessionId, login, ipAddress, host); + session.setAttribute(Constants.SESSION_ID_ATTR, sessionId); + session.setMaxInactiveInterval(Constants.SESSION_TIMEOUT_SECONDS); + } + } +} diff --git a/src/main/java/ru/ulstu/fc/user/UserSessionLogoutHandler.java b/src/main/java/ru/ulstu/fc/user/UserSessionLogoutHandler.java new file mode 100644 index 0000000..2063de8 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserSessionLogoutHandler.java @@ -0,0 +1,48 @@ +package ru.ulstu.fc.user; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import org.springframework.stereotype.Component; +import ru.ulstu.fc.config.Constants; + +import java.io.IOException; + +@Component +public class UserSessionLogoutHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler { + private final Logger log = LoggerFactory.getLogger(UserSessionLogoutHandler.class); + private final UserSessionService userSessionService; + + public UserSessionLogoutHandler(UserSessionService userSessionService) { + this.userSessionService = userSessionService; + setDefaultTargetUrl(Constants.LOGOUT_URL); + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + if (authentication == null) { + super.onLogoutSuccess(request, response, authentication); + return; + } + final String login = authentication.getName(); + final String ipAddress = IpAddressResolver.getRemoteAddr(request); + final String host = request.getRemoteHost(); + log.debug("Logout Success for {}@{} ({})", login, ipAddress, host); + HttpSession session = request.getSession(false); + if (session != null) { + final String sessionId = session.getAttribute(Constants.SESSION_ID_ATTR).toString(); + userSessionService.closeUserSession(sessionId); + session.removeAttribute(Constants.SESSION_ID_ATTR); + session.invalidate(); + } + super.onLogoutSuccess(request, response, authentication); + } +} diff --git a/src/main/java/ru/ulstu/fc/user/UserSessionRepository.java b/src/main/java/ru/ulstu/fc/user/UserSessionRepository.java new file mode 100644 index 0000000..ff28f93 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserSessionRepository.java @@ -0,0 +1,13 @@ +package ru.ulstu.fc.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.fc.user.model.UserSession; + +import java.util.Date; +import java.util.List; + +public interface UserSessionRepository extends JpaRepository { + UserSession findOneBySessionId(String sessionId); + + List findAllByLogoutTimeIsNullAndLoginTimeBefore(Date date); +} diff --git a/src/main/java/ru/ulstu/fc/user/UserSessionService.java b/src/main/java/ru/ulstu/fc/user/UserSessionService.java new file mode 100644 index 0000000..7f6e5ce --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserSessionService.java @@ -0,0 +1,40 @@ +package ru.ulstu.fc.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.fc.user.model.User; +import ru.ulstu.fc.user.model.UserSession; + +@Service +@Transactional +public class UserSessionService { + private final Logger log = LoggerFactory.getLogger(UserSessionService.class); + private final UserSessionRepository userSessionRepository; + private final UserService userService; + + public UserSessionService(UserSessionRepository userSessionRepository, UserService userService) { + this.userSessionRepository = userSessionRepository; + this.userService = userService; + } + + public void createUserSession(String sessionId, String login, String ipAddress, String host) { + final User user = userService.getUserByLogin(login); + if (user == null) { + throw new UserNotFoundException(login); + } + userSessionRepository.save(new UserSession(sessionId, ipAddress, host, user)); + log.debug("User session {} created for user {}@{} ({})", sessionId, login, ipAddress, host); + } + + public void closeUserSession(String sessionId) { + final UserSession userSession = userSessionRepository.findOneBySessionId(sessionId); + if (userSession == null) { + throw new IllegalArgumentException(String.format("User session %s not found", sessionId)); + } + userSession.close(); + userSessionRepository.save(userSession); + log.debug("User session {} closed", sessionId); + } +} diff --git a/src/main/java/ru/ulstu/fc/user/UserUtils.java b/src/main/java/ru/ulstu/fc/user/UserUtils.java new file mode 100644 index 0000000..a7169c8 --- /dev/null +++ b/src/main/java/ru/ulstu/fc/user/UserUtils.java @@ -0,0 +1,24 @@ +package ru.ulstu.fc.user; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +public class UserUtils { + public static String getCurrentUserLogin() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext == null) { + return null; + } + final Authentication authentication = securityContext.getAuthentication(); + if (authentication.getPrincipal() instanceof UserDetails) { + final UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } + if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + } +} diff --git a/src/main/java/ru/ulstu/fc/core/model/User.java b/src/main/java/ru/ulstu/fc/user/model/User.java similarity index 96% rename from src/main/java/ru/ulstu/fc/core/model/User.java rename to src/main/java/ru/ulstu/fc/user/model/User.java index 2d960b1..11b3162 100644 --- a/src/main/java/ru/ulstu/fc/core/model/User.java +++ b/src/main/java/ru/ulstu/fc/user/model/User.java @@ -1,4 +1,4 @@ -package ru.ulstu.fc.core.model; +package ru.ulstu.fc.user.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -10,6 +10,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import ru.ulstu.fc.config.Constants; +import ru.ulstu.fc.core.model.BaseEntity; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/ru/ulstu/fc/core/model/UserRole.java b/src/main/java/ru/ulstu/fc/user/model/UserRole.java similarity index 97% rename from src/main/java/ru/ulstu/fc/core/model/UserRole.java rename to src/main/java/ru/ulstu/fc/user/model/UserRole.java index 92ecbb7..c6375cd 100644 --- a/src/main/java/ru/ulstu/fc/core/model/UserRole.java +++ b/src/main/java/ru/ulstu/fc/user/model/UserRole.java @@ -1,4 +1,4 @@ -package ru.ulstu.fc.core.model; +package ru.ulstu.fc.user.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/ru/ulstu/fc/core/model/UserRoleConstants.java b/src/main/java/ru/ulstu/fc/user/model/UserRoleConstants.java similarity index 81% rename from src/main/java/ru/ulstu/fc/core/model/UserRoleConstants.java rename to src/main/java/ru/ulstu/fc/user/model/UserRoleConstants.java index ccc52ad..4eae200 100644 --- a/src/main/java/ru/ulstu/fc/core/model/UserRoleConstants.java +++ b/src/main/java/ru/ulstu/fc/user/model/UserRoleConstants.java @@ -1,4 +1,4 @@ -package ru.ulstu.fc.core.model; +package ru.ulstu.fc.user.model; public class UserRoleConstants { public static final String ADMIN = "ROLE_ADMIN"; diff --git a/src/main/java/ru/ulstu/fc/core/model/UserSession.java b/src/main/java/ru/ulstu/fc/user/model/UserSession.java similarity index 96% rename from src/main/java/ru/ulstu/fc/core/model/UserSession.java rename to src/main/java/ru/ulstu/fc/user/model/UserSession.java index e217f7e..354402d 100644 --- a/src/main/java/ru/ulstu/fc/core/model/UserSession.java +++ b/src/main/java/ru/ulstu/fc/user/model/UserSession.java @@ -1,4 +1,4 @@ -package ru.ulstu.fc.core.model; +package ru.ulstu.fc.user.model; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -8,6 +8,7 @@ import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import jakarta.validation.constraints.NotNull; +import ru.ulstu.fc.core.model.BaseEntity; import java.util.Date;