diff --git a/build.gradle b/build.gradle index 53e4666..a2e618d 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,9 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' + } tasks.named('test') { diff --git a/data.mv.db b/data.mv.db index 04dc635..de4ff61 100644 Binary files a/data.mv.db and b/data.mv.db differ diff --git a/src/main/java/com/webproglabs/lab1/PasswordEncoderConfiguration.java b/src/main/java/com/webproglabs/lab1/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..8c560b3 --- /dev/null +++ b/src/main/java/com/webproglabs/lab1/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package com.webproglabs.lab1; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public PasswordEncoder createPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java b/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java new file mode 100644 index 0000000..0d0b1cf --- /dev/null +++ b/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java @@ -0,0 +1,71 @@ +package com.webproglabs.lab1; + +import com.webproglabs.lab1.lab34.controller.mvc_controllers.UserSignupMvcController; +import com.webproglabs.lab1.lab34.model.UserRole; +import com.webproglabs.lab1.lab34.services.ProfileService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +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; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static final String LOGIN_URL = "/login"; + private final ProfileService userService; + + public SecurityConfiguration(ProfileService userService) { + this.userService = userService; + createAdminOnStartup(); + } + + private void createAdminOnStartup() { + final String admin = "admin"; + if (userService.findByLogin(admin) == null) { + log.info("Admin user successfully created"); + try { + userService.createUser(admin, admin, admin, UserRole.ADMIN); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.headers().frameOptions().sameOrigin().and() + .cors().and() + .csrf().disable() + .authorizeRequests() + .antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll() + .antMatchers(HttpMethod.GET, LOGIN_URL).permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage(LOGIN_URL).permitAll() + .and() + .logout().permitAll(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userService); + } + + @Override + public void configure(WebSecurity web) { + web.ignoring() + .antMatchers("/css/**") + .antMatchers("/js/**") + .antMatchers("/templates/**") + .antMatchers("/webjars/**"); + } +} \ No newline at end of file diff --git a/src/main/java/com/webproglabs/lab1/WebConfiguration.java b/src/main/java/com/webproglabs/lab1/WebConfiguration.java index 0a5a2a5..ef50db9 100644 --- a/src/main/java/com/webproglabs/lab1/WebConfiguration.java +++ b/src/main/java/com/webproglabs/lab1/WebConfiguration.java @@ -11,7 +11,7 @@ public class WebConfiguration implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { WebMvcConfigurer.super.addViewControllers(registry); - registry.addViewController("rest-test"); + registry.addViewController("login"); } @Override public void addCorsMappings(CorsRegistry registry) { diff --git a/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileDto.java b/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileDto.java index 8b97a48..41f4aa3 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileDto.java +++ b/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileDto.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Post; import com.webproglabs.lab1.lab34.model.Profile; +import com.webproglabs.lab1.lab34.model.UserRole; import java.util.ArrayList; import java.util.List; @@ -16,12 +17,15 @@ public class ProfileDto { private List posts = new ArrayList<>(); + private UserRole role; + public ProfileDto(){} public ProfileDto(Profile profile){ this.id = profile.getId(); this.login = profile.getLogin(); this.password = profile.getPassword(); + this.role = profile.getRole(); for(Comment comment: profile.getComments()){ comments.add(new CommentDto(comment)); } @@ -37,6 +41,9 @@ public class ProfileDto { public String getLogin() {return login;} public void setLogin(String login) {this.login = login;} public String getPassword() {return password;} + + public UserRole getRole() {return role;} + public void setPassword(String password) {this.password = password;} public List getComments() {return comments;} public List getPosts() {return posts;} diff --git a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/FeedMvcController.java b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/FeedMvcController.java index a173e4c..3985e0d 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/FeedMvcController.java +++ b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/FeedMvcController.java @@ -3,9 +3,12 @@ package com.webproglabs.lab1.lab34.controller.mvc_controllers; import com.webproglabs.lab1.lab34.controller.CommentDto; import com.webproglabs.lab1.lab34.controller.PostDto; import com.webproglabs.lab1.lab34.controller.ProfileDto; +import com.webproglabs.lab1.lab34.model.Profile; import com.webproglabs.lab1.lab34.services.CommentService; import com.webproglabs.lab1.lab34.services.PostService; import com.webproglabs.lab1.lab34.services.ProfileService; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -26,6 +29,12 @@ public class FeedMvcController { @GetMapping public String getFeedPage(Model model) { + UserDetails principal = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + var user = profileService.findByLogin(principal.getUsername()); + if (user != null) { + return "redirect:/feed/" + user.getId().toString(); + } + model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList()); return "feed"; } diff --git a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/ProfileMvcController.java b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/ProfileMvcController.java index 0621cff..a59470f 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/ProfileMvcController.java +++ b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/ProfileMvcController.java @@ -1,7 +1,9 @@ package com.webproglabs.lab1.lab34.controller.mvc_controllers; import com.webproglabs.lab1.lab34.controller.ProfileDto; +import com.webproglabs.lab1.lab34.model.UserRole; import com.webproglabs.lab1.lab34.services.ProfileService; +import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -23,6 +25,7 @@ public class ProfileMvcController { } @GetMapping(value={"profiles"}) + @Secured({UserRole.AsString.ADMIN}) public String getProfiles(Model model) { model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList()); model.addAttribute("profileDto", new ProfileDto()); diff --git a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupDto.java b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupDto.java new file mode 100644 index 0000000..447b057 --- /dev/null +++ b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupDto.java @@ -0,0 +1,40 @@ +package com.webproglabs.lab1.lab34.controller.mvc_controllers; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +public class UserSignupDto { + @NotBlank + @Size(min = 3, max = 64) + private String login; + @NotBlank + @Size(min = 6, max = 64) + private String password; + @NotBlank + @Size(min = 6, max = 64) + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordConfirm() { + return passwordConfirm; + } + + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupMvcController.java b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupMvcController.java new file mode 100644 index 0000000..6f4f399 --- /dev/null +++ b/src/main/java/com/webproglabs/lab1/lab34/controller/mvc_controllers/UserSignupMvcController.java @@ -0,0 +1,49 @@ +package com.webproglabs.lab1.lab34.controller.mvc_controllers; + +import com.webproglabs.lab1.lab34.model.Profile; +import com.webproglabs.lab1.lab34.services.ProfileService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.validation.Valid; + +@Controller +@RequestMapping(UserSignupMvcController.SIGNUP_URL) +public class UserSignupMvcController { + public static final String SIGNUP_URL = "/signup"; + + private final ProfileService userService; + + public UserSignupMvcController(ProfileService userService) { + this.userService = userService; + } + + @GetMapping + public String showSignupForm(Model model) { + model.addAttribute("userDto", new UserSignupDto()); + return "signup"; + } + + @PostMapping + public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + model.addAttribute("errors", bindingResult.getAllErrors()); + return "signup"; + } + try { + final Profile user = userService.createUser( + userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm()); + return "redirect:/login?created=" + user.getLogin(); + } catch (Exception e) { + model.addAttribute("errors", e.getMessage()); + return "signup"; + } + } +} diff --git a/src/main/java/com/webproglabs/lab1/lab34/model/Profile.java b/src/main/java/com/webproglabs/lab1/lab34/model/Profile.java index 79702d6..be15025 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/model/Profile.java +++ b/src/main/java/com/webproglabs/lab1/lab34/model/Profile.java @@ -1,6 +1,8 @@ package com.webproglabs.lab1.lab34.model; import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -12,9 +14,22 @@ public class Profile { @GeneratedValue(strategy = GenerationType.AUTO) private Long id; + @Column(nullable = false, unique = true, length = 64) + @NotBlank + @Size(min = 3, max = 64) private String login; + + @Column(nullable = false, length = 64) + @NotBlank + @Size(min = 6, max = 64) private String password; + private UserRole role; + + public UserRole getRole() { + return role; + } + @OneToMany(mappedBy = "owner", orphanRemoval = true, fetch = FetchType.EAGER) private List comments = new ArrayList(); @@ -25,6 +40,18 @@ public class Profile { public Profile(String login, String password, List comments, List posts) { this.login = login; this.password=password; + this.role = UserRole.USER; + for (int i = 0; i < comments.size(); i++) { + addComment(comments.get(i)); + } + for (int i = 0; i < posts.size(); i++) { + addPost(posts.get(i)); + } + }; + public Profile(String login, String password, List comments, List posts, UserRole role) { + this.login = login; + this.password=password; + this.role = role; for (int i = 0; i < comments.size(); i++) { addComment(comments.get(i)); } diff --git a/src/main/java/com/webproglabs/lab1/lab34/model/UserRole.java b/src/main/java/com/webproglabs/lab1/lab34/model/UserRole.java new file mode 100644 index 0000000..03102cc --- /dev/null +++ b/src/main/java/com/webproglabs/lab1/lab34/model/UserRole.java @@ -0,0 +1,20 @@ +package com.webproglabs.lab1.lab34.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum UserRole implements GrantedAuthority { + ADMIN, + USER; + + private static final String PREFIX = "ROLE_"; + + @Override + public String getAuthority() { + return PREFIX + this.name(); + } + + public static final class AsString { + public static final String ADMIN = PREFIX + "ADMIN"; + public static final String USER = PREFIX + "USER"; + } +} diff --git a/src/main/java/com/webproglabs/lab1/lab34/repository/ProfileRepository.java b/src/main/java/com/webproglabs/lab1/lab34/repository/ProfileRepository.java index 1497572..0f0be17 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/repository/ProfileRepository.java +++ b/src/main/java/com/webproglabs/lab1/lab34/repository/ProfileRepository.java @@ -8,4 +8,5 @@ import java.util.List; public interface ProfileRepository extends JpaRepository { List findByLoginLike(String login); + Profile findOneByLoginIgnoreCase(String login); } diff --git a/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java b/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java index 1be9ffe..7a5febd 100644 --- a/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java +++ b/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java @@ -3,23 +3,28 @@ package com.webproglabs.lab1.lab34.services; import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Post; import com.webproglabs.lab1.lab34.model.Profile; -import com.webproglabs.lab1.lab34.repository.CommentRepository; +import com.webproglabs.lab1.lab34.model.UserRole; import com.webproglabs.lab1.lab34.repository.ProfileRepository; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.persistence.EntityNotFoundException; import javax.transaction.Transactional; -import java.util.List; -import java.util.Optional; +import java.util.*; @Service -public class ProfileService { +public class ProfileService implements UserDetailsService { private final ProfileRepository profileRepository; + private final PasswordEncoder passwordEncoder; - public ProfileService(ProfileRepository profileRepository) { + public ProfileService(ProfileRepository profileRepository, PasswordEncoder passwordEncoder) { this.profileRepository = profileRepository; + this.passwordEncoder = passwordEncoder; } @Transactional @@ -44,7 +49,40 @@ public class ProfileService { if (!StringUtils.hasText(login) || !StringUtils.hasText(password)) { throw new IllegalArgumentException("User data is null or empty"); } - final Profile user = new Profile(login, password, comments, posts); + final Profile user = new Profile(login, password, comments, posts, UserRole.USER); + return profileRepository.save(user); + } + + @Transactional + public Profile addUser(String login, String password, List comments, List posts, UserRole role) { + if (!StringUtils.hasText(login) || !StringUtils.hasText(password)) { + throw new IllegalArgumentException("User data is null or empty"); + } + final Profile user = new Profile(login, password, comments, posts, role); + return profileRepository.save(user); + } + + @Transactional + public Profile createUser(String login, String password, String passwordConfirm) throws Exception { + if (findByLogin(login) != null) { + throw new Exception("User " + login + " already exists"); + } + final Profile user = new Profile(login, passwordEncoder.encode(password), new ArrayList<>(), new ArrayList<>(), UserRole.USER); + if (!Objects.equals(password, passwordConfirm)) { + throw new Exception("Passwords not equals"); + } + return profileRepository.save(user); + } + + @Transactional + public Profile createUser(String login, String password, String passwordConfirm, UserRole role) throws Exception { + if (findByLogin(login) != null) { + throw new Exception("User " + login + " already exists"); + } + final Profile user = new Profile(login, passwordEncoder.encode(password), new ArrayList<>(), new ArrayList<>(), role); + if (!Objects.equals(password, passwordConfirm)) { + throw new Exception("Passwords not equals"); + } return profileRepository.save(user); } @@ -70,4 +108,17 @@ public class ProfileService { public void deleteAllUsers() { profileRepository.deleteAll(); } + + public Profile findByLogin(String login) { + return profileRepository.findOneByLoginIgnoreCase(login); + } + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final Profile userEntity = findByLogin(username); + if (userEntity == null) { + throw new UsernameNotFoundException(username); + } + return new org.springframework.security.core.userdetails.User( + userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole())); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index da7b0b1..8bb0d85 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,3 +9,5 @@ spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.settings.trace=false spring.h2.console.settings.web-allow-others=false +# Security +spring.security.user.password=user diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html index 706ec28..119f8f7 100644 --- a/src/main/resources/templates/default.html +++ b/src/main/resources/templates/default.html @@ -1,7 +1,9 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" + xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> +> Лабораторная работа 5 @@ -21,8 +23,11 @@ diff --git a/src/main/resources/templates/feed.html b/src/main/resources/templates/feed.html index 13aee47..3bb2e58 100644 --- a/src/main/resources/templates/feed.html +++ b/src/main/resources/templates/feed.html @@ -8,19 +8,19 @@
- + + + + + + + + + + + + +
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..5c9f583 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,30 @@ + + + +
+
+ Пользователь не найден или пароль указан не верно +
+
+ Выход успешно произведен +
+
+ Пользователь '' успешно создан +
+
+
+ +
+
+ +
+ + Регистрация +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..8cd75f5 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,28 @@ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+ + Назад +
+
+
+ + \ No newline at end of file