diff --git a/build.gradle b/build.gradle index e339ff5..11f98dc 100644 --- a/build.gradle +++ b/build.gradle @@ -37,10 +37,10 @@ dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation' implementation group: 'org.springframework.boot', name:'spring-boot-starter-data-jpa' - //implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security' + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security' implementation group: 'org.slf4j', name: 'slf4j-api', version: versionSLF4J implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect' - //implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5' + implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5' implementation group: 'com.h2database', name:'h2' implementation group: 'javax.xml.bind', name:'jaxb-api' implementation group: 'org.javassist', name:'javassist' diff --git a/data/db.mv.db b/data/db.mv.db index 8fe3c2f..d6e4cd9 100644 Binary files a/data/db.mv.db and b/data/db.mv.db differ diff --git a/src/main/java/ru/ulstu/SeminarApplication.java b/src/main/java/ru/ulstu/SeminarApplication.java index fb6201f..04b8e36 100644 --- a/src/main/java/ru/ulstu/SeminarApplication.java +++ b/src/main/java/ru/ulstu/SeminarApplication.java @@ -2,10 +2,25 @@ package ru.ulstu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import ru.ulstu.user.UserService; @SpringBootApplication public class SeminarApplication { + private final UserService userService; + + public SeminarApplication(UserService userService) { + this.userService = userService; + } + public static void main(String[] args) { SpringApplication.run(SeminarApplication.class, args); } + + @EventListener(ApplicationReadyEvent.class) + public void doSomethingAfterStartup() { + System.out.println("hello world, I have just started up"); + userService.initDefaultAdmin(); + } } diff --git a/src/main/java/ru/ulstu/configuration/Constants.java b/src/main/java/ru/ulstu/configuration/Constants.java new file mode 100644 index 0000000..1cd4983 --- /dev/null +++ b/src/main/java/ru/ulstu/configuration/Constants.java @@ -0,0 +1,12 @@ +package ru.ulstu.configuration; + +public class Constants { + public static final int MIN_PASSWORD_LENGTH = 6; + + public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$"; + + public static final String COOKIES_NAME = "JSESSIONID"; + public static final String LOGOUT_URL = "/login?logout"; + public static final String SESSION_ID_ATTR = "sessionId"; + public static final int SESSION_TIMEOUT_SECONDS = 30 * 60; +} diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java index 2599a53..9154347 100644 --- a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java @@ -20,6 +20,8 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; public class MvcConfiguration implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login"); + registry.addViewController("/loginError"); registry.addViewController("/index"); registry.addViewController("/admin"); registry.addViewController("/editNews"); diff --git a/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..19370da --- /dev/null +++ b/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java @@ -0,0 +1,13 @@ +package ru.ulstu.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java new file mode 100644 index 0000000..f893257 --- /dev/null +++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java @@ -0,0 +1,85 @@ +package ru.ulstu.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import ru.ulstu.model.UserRoleConstants; +import ru.ulstu.user.UserService; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + + private final UserService userService; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final AuthenticationSuccessHandler authenticationSuccessHandler; + private final LogoutSuccessHandler logoutSuccessHandler; + + public SecurityConfiguration(UserService userService, + BCryptPasswordEncoder bCryptPasswordEncoder, + AuthenticationSuccessHandler authenticationSuccessHandler, + LogoutSuccessHandler logoutSuccessHandler) { + this.userService = userService; + this.bCryptPasswordEncoder = bCryptPasswordEncoder; + this.authenticationSuccessHandler = authenticationSuccessHandler; + this.logoutSuccessHandler = logoutSuccessHandler; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + http.headers().frameOptions().disable(); + log.debug("Security enabled"); + http.authorizeRequests() + .antMatchers("/").permitAll() + .antMatchers("/login", "/index", "/news/**", "/h2-console/*", "/h2-console").permitAll() + .antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN) + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .failureUrl("/loginError") + .successHandler(authenticationSuccessHandler) + .permitAll() + .and() + .logout() + .logoutSuccessHandler(logoutSuccessHandler) + .logoutSuccessUrl(Constants.LOGOUT_URL) + .invalidateHttpSession(false) + .clearAuthentication(true) + .deleteCookies(Constants.COOKIES_NAME) + .permitAll(); + } + + @Override + public void configure(WebSecurity web) { + web.ignoring() + .antMatchers("/css/**") + .antMatchers("/js/**") + .antMatchers("/img/**") + .antMatchers("/templates/**") + .antMatchers("/webjars/**"); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + try { + auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder); + } catch (Exception e) { + throw new BeanInitializationException("Security configuration failed", e); + } + } +} diff --git a/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java b/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java index ca7591b..f9ab6a7 100644 --- a/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/TemplateConfiguration.java @@ -7,11 +7,9 @@ package ru.ulstu.configuration; import nz.net.ultraq.thymeleaf.LayoutDialect; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.templateresolver.ITemplateResolver; @@ -19,10 +17,11 @@ import org.thymeleaf.templateresolver.ITemplateResolver; public class TemplateConfiguration { @Bean - public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { + public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver, SpringSecurityDialect sec) { final SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.addTemplateResolver(templateResolver); templateEngine.addDialect(new LayoutDialect()); + templateEngine.addDialect(sec); return templateEngine; } } diff --git a/src/main/java/ru/ulstu/controller/IndexController.java b/src/main/java/ru/ulstu/controller/IndexController.java index d6f2fc0..9774e53 100644 --- a/src/main/java/ru/ulstu/controller/IndexController.java +++ b/src/main/java/ru/ulstu/controller/IndexController.java @@ -22,7 +22,7 @@ public class IndexController { @GetMapping("/") public String index(Model model) { - model.addAttribute("news", newsService.getAll()); + model.addAttribute("news", newsService.getLast()); return "index"; } } diff --git a/src/main/java/ru/ulstu/controller/NewsController.java b/src/main/java/ru/ulstu/controller/NewsController.java index 3de7221..111a1ca 100644 --- a/src/main/java/ru/ulstu/controller/NewsController.java +++ b/src/main/java/ru/ulstu/controller/NewsController.java @@ -13,12 +13,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import ru.ulstu.model.News; import ru.ulstu.service.NewsService; import javax.validation.Valid; @Controller +@RequestMapping("news") public class NewsController { private final NewsService newsService; @@ -26,6 +28,12 @@ public class NewsController { this.newsService = newsService; } + @GetMapping("/news") + public String index(Model model) { + model.addAttribute("news", newsService.getAll()); + return "news"; + } + @GetMapping("/editNews/{newsId}") public String editNews(@PathVariable(value = "newsId") Integer id, Model model) { model.addAttribute("news", (id != null && id != 0) ? newsService.getById(id) : new News()); @@ -35,7 +43,7 @@ public class NewsController { @GetMapping("/news/{newsId}") public String viewNews(@PathVariable(value = "newsId") Integer id, Model model) { model.addAttribute("news", id != null ? newsService.getById(id) : new News()); - return "news"; + return "viewNews"; } @PostMapping("saveNews") @@ -45,12 +53,12 @@ public class NewsController { } newsService.save(news); - return "redirect:/"; + return "redirect:/news/news"; } @GetMapping("deleteNews/{newsId}") public String delete(@PathVariable(value = "newsId") Integer id) { newsService.delete(id); - return "redirect:/"; + return "redirect:/news/news"; } } diff --git a/src/main/java/ru/ulstu/model/User.java b/src/main/java/ru/ulstu/model/User.java new file mode 100644 index 0000000..5db0c57 --- /dev/null +++ b/src/main/java/ru/ulstu/model/User.java @@ -0,0 +1,71 @@ +package ru.ulstu.model; + +import ru.ulstu.configuration.Constants; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "is_users") +public class User extends BaseEntity { + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @NotNull + @Size(min = 60, max = 60) + @Column(name = "password_hash", length = 60, nullable = false) + private String password; + + @ManyToMany + @JoinTable( + name = "is_user_role", + joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, + inverseJoinColumns = {@JoinColumn(name = "user_role_name", referencedColumnName = "name")}) + private Set roles; + + public User() { + roles = new HashSet<>(); + } + + public User(String login, String password, Set roles) { + this.login = login; + this.password = password; + this.roles = roles; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login.toLowerCase(); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } +} diff --git a/src/main/java/ru/ulstu/model/UserRole.java b/src/main/java/ru/ulstu/model/UserRole.java new file mode 100644 index 0000000..6e0a45d --- /dev/null +++ b/src/main/java/ru/ulstu/model/UserRole.java @@ -0,0 +1,50 @@ +package ru.ulstu.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Entity +@Table(name = "is_user_roles") +public class UserRole { + @Id + @NotNull + @Size(max = 50) + @Column(length = 50, nullable = false) + private String name; + + public UserRole() { + } + + public UserRole(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UserRole role = (UserRole) o; + return !(name != null ? !name.equals(role.name) : role.name != null); + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } +} diff --git a/src/main/java/ru/ulstu/model/UserRoleConstants.java b/src/main/java/ru/ulstu/model/UserRoleConstants.java new file mode 100644 index 0000000..cf78f0c --- /dev/null +++ b/src/main/java/ru/ulstu/model/UserRoleConstants.java @@ -0,0 +1,6 @@ +package ru.ulstu.model; + +public class UserRoleConstants { + public static final String ADMIN = "ROLE_ADMIN"; + public static final String USER = "ROLE_USER"; +} diff --git a/src/main/java/ru/ulstu/model/UserSession.java b/src/main/java/ru/ulstu/model/UserSession.java new file mode 100644 index 0000000..42e9838 --- /dev/null +++ b/src/main/java/ru/ulstu/model/UserSession.java @@ -0,0 +1,103 @@ +package ru.ulstu.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotNull; +import java.util.Date; + +@Entity +@Table(name = "is_user_sessions") +public class UserSession extends BaseEntity { + @NotNull + @Column(name = "session_id", nullable = false, unique = true) + private String sessionId; + + @NotNull + @Column(name = "ip_address", nullable = false) + private String ipAddress; + + @NotNull + @Column(nullable = false) + private String host; + + @NotNull + @Column(name = "login_time", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date loginTime; + + @Column(name = "logout_time") + @Temporal(TemporalType.TIMESTAMP) + private Date logoutTime; + + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") + private User user; + + public UserSession() { + } + + public UserSession(String sessionId, String ipAddress, String host, User user) { + this.sessionId = sessionId; + this.ipAddress = ipAddress; + this.host = host; + this.loginTime = new Date(); + this.user = user; + } + + public String getSessionId() { + return sessionId; + } + + public String getIpAddress() { + return ipAddress; + } + + public String getHost() { + return host; + } + + public Date getLoginTime() { + return loginTime; + } + + public Date getLogoutTime() { + return logoutTime; + } + + public User getUser() { + return user; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public void setHost(String host) { + this.host = host; + } + + public void setLoginTime(Date loginTime) { + this.loginTime = loginTime; + } + + public void setLogoutTime(Date logoutTime) { + this.logoutTime = logoutTime; + } + + public void setUser(User user) { + this.user = user; + } + + public void close() { + this.logoutTime = new Date(); + } +} diff --git a/src/main/java/ru/ulstu/repository/NewsRepository.java b/src/main/java/ru/ulstu/repository/NewsRepository.java index 3aaf6bf..6bd607a 100644 --- a/src/main/java/ru/ulstu/repository/NewsRepository.java +++ b/src/main/java/ru/ulstu/repository/NewsRepository.java @@ -3,5 +3,8 @@ package ru.ulstu.repository; import org.springframework.data.jpa.repository.JpaRepository; import ru.ulstu.model.News; +import java.util.List; + public interface NewsRepository extends JpaRepository { + List findFirst3ByOrderByDateDesc(); } diff --git a/src/main/java/ru/ulstu/service/NewsService.java b/src/main/java/ru/ulstu/service/NewsService.java index 293d062..3f743f0 100644 --- a/src/main/java/ru/ulstu/service/NewsService.java +++ b/src/main/java/ru/ulstu/service/NewsService.java @@ -46,4 +46,8 @@ public class NewsService { public void delete(Integer id) { newsRepository.deleteById(id); } + + public List getLast() { + return newsRepository.findFirst3ByOrderByDateDesc(); + } } diff --git a/src/main/java/ru/ulstu/user/IpAddressResolver.java b/src/main/java/ru/ulstu/user/IpAddressResolver.java new file mode 100644 index 0000000..0ed8a60 --- /dev/null +++ b/src/main/java/ru/ulstu/user/IpAddressResolver.java @@ -0,0 +1,23 @@ +package ru.ulstu.user; + +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +public final class IpAddressResolver { + private static final String CLIENT_IP_HEADER = "Client-IP"; + private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For"; + + public static String getRemoteAddr(HttpServletRequest request) { + String headerClientIp = request.getHeader(""); + String headerXForwardedFor = request.getHeader(HttpServletRequest.FORM_AUTH); + if (StringUtils.isEmpty(request.getRemoteAddr()) && !StringUtils.isEmpty(headerClientIp)) { + return headerClientIp; + } + if (!StringUtils.isEmpty(headerXForwardedFor)) { + return headerXForwardedFor; + } + return request.getRemoteAddr(); + } + +} diff --git a/src/main/java/ru/ulstu/user/UserNotFoundException.java b/src/main/java/ru/ulstu/user/UserNotFoundException.java new file mode 100644 index 0000000..73d70a5 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.user; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/UserRepository.java b/src/main/java/ru/ulstu/user/UserRepository.java new file mode 100644 index 0000000..bc44854 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserRepository.java @@ -0,0 +1,15 @@ +package ru.ulstu.user; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.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/user/UserRoleRepository.java b/src/main/java/ru/ulstu/user/UserRoleRepository.java new file mode 100644 index 0000000..79c3aa4 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserRoleRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.model.UserRole; + +public interface UserRoleRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/user/UserService.java b/src/main/java/ru/ulstu/user/UserService.java new file mode 100644 index 0000000..752f234 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserService.java @@ -0,0 +1,72 @@ +package ru.ulstu.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.model.User; +import ru.ulstu.model.UserRole; +import ru.ulstu.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; + + 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.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); + user.setPassword(passwordEncoder.encode(user.getPassword())); + user = userRepository.save(user); + log.debug("Created Information for User: {}", user.getLogin()); + return user; + } + + public void initDefaultAdmin() { + String adminLogin = "admin"; + String adminPassword = "adminadmin"; + if (getUserByLogin(adminLogin) == null) { + UserRole adminRole = userRoleRepository.save(new UserRole(UserRoleConstants.ADMIN)); + createUser(new User(adminLogin, adminPassword, Set.of(adminRole))); + } + } +} diff --git a/src/main/java/ru/ulstu/user/UserSessionLoginHandler.java b/src/main/java/ru/ulstu/user/UserSessionLoginHandler.java new file mode 100644 index 0000000..dd62ef9 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserSessionLoginHandler.java @@ -0,0 +1,44 @@ +package ru.ulstu.user; + +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.configuration.Constants; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +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/user/UserSessionLogoutHandler.java b/src/main/java/ru/ulstu/user/UserSessionLogoutHandler.java new file mode 100644 index 0000000..87979d3 --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserSessionLogoutHandler.java @@ -0,0 +1,48 @@ +package ru.ulstu.user; + +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.configuration.Constants; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +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/user/UserSessionRepository.java b/src/main/java/ru/ulstu/user/UserSessionRepository.java new file mode 100644 index 0000000..54e063a --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserSessionRepository.java @@ -0,0 +1,13 @@ +package ru.ulstu.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.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/user/UserSessionService.java b/src/main/java/ru/ulstu/user/UserSessionService.java new file mode 100644 index 0000000..dd06ffa --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserSessionService.java @@ -0,0 +1,40 @@ +package ru.ulstu.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.model.User; +import ru.ulstu.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/user/UserUtils.java b/src/main/java/ru/ulstu/user/UserUtils.java new file mode 100644 index 0000000..27a453f --- /dev/null +++ b/src/main/java/ru/ulstu/user/UserUtils.java @@ -0,0 +1,24 @@ +package ru.ulstu.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/resources/public/css/main.css b/src/main/resources/public/css/main.css index 359a34c..9b299ad 100644 --- a/src/main/resources/public/css/main.css +++ b/src/main/resources/public/css/main.css @@ -29,6 +29,6 @@ color: black; } -.link-dark, .link-dark:visited, .link-dark:focus { +.link-dark, .link-dark:visited, .link-dark:focus, .link-dark:any-link { color: black; } \ No newline at end of file diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index f1bcb60..f37ada4 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -5,12 +5,10 @@ --> - + diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html index 819bd2a..3146bbf 100644 --- a/src/main/resources/templates/default.html +++ b/src/main/resources/templates/default.html @@ -5,8 +5,9 @@ --> - + app-name @@ -32,7 +33,7 @@ diff --git a/src/main/resources/templates/editNews.html b/src/main/resources/templates/editNews.html index 44fc982..71a8aad 100644 --- a/src/main/resources/templates/editNews.html +++ b/src/main/resources/templates/editNews.html @@ -10,7 +10,7 @@ layout:decorate="~{default}">

Редактирование новости:

-
+ diff --git a/src/main/resources/templates/error/403.html b/src/main/resources/templates/error/403.html new file mode 100644 index 0000000..9eb069c --- /dev/null +++ b/src/main/resources/templates/error/403.html @@ -0,0 +1,13 @@ + + + + + +
+
Доступ запрещён
+
Вернуться на главную
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/error/404.html b/src/main/resources/templates/error/404.html new file mode 100644 index 0000000..d599650 --- /dev/null +++ b/src/main/resources/templates/error/404.html @@ -0,0 +1,13 @@ + + + + + +
+
Страница не найдена
+
Вернуться на главную
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..b77a6cf --- /dev/null +++ b/src/main/resources/templates/error/500.html @@ -0,0 +1,13 @@ + + + + + +
+
Ошибка сервера
+
Вернуться на главную
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 4b18b01..81c63a7 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -5,9 +5,8 @@ --> - +
@@ -17,16 +16,7 @@
-
-
-
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..20cb039 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,35 @@ + + + + + + +
+
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/loginError.html b/src/main/resources/templates/loginError.html new file mode 100644 index 0000000..1d08df3 --- /dev/null +++ b/src/main/resources/templates/loginError.html @@ -0,0 +1,40 @@ + + + + + + +
+
+ +
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/news.html b/src/main/resources/templates/news.html index 06b6543..1f6edf8 100644 --- a/src/main/resources/templates/news.html +++ b/src/main/resources/templates/news.html @@ -5,21 +5,36 @@ --> - +
-
-
- -
-
-
-
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
-
- Назад
diff --git a/src/main/resources/templates/viewNews.html b/src/main/resources/templates/viewNews.html new file mode 100644 index 0000000..06b6543 --- /dev/null +++ b/src/main/resources/templates/viewNews.html @@ -0,0 +1,25 @@ + + + + +
+
+
+ +
+
+
+
+
+
+
+ Назад +
+