diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..a90eceb
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ mysql.8
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/sweater_db
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/compose.yml b/compose.yml
index f1b675b..0d844f6 100644
--- a/compose.yml
+++ b/compose.yml
@@ -7,5 +7,5 @@ services:
- 'MYSQL_ROOT_PASSWORD=verysecret'
- 'MYSQL_USER=user'
ports:
- - '3306'
+ - '3306:3306'
diff --git a/pom.xml b/pom.xml
index 4635ced..c939ddb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,15 @@
mysql-connector-j
runtime
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-core
+ 6.3.1
+
diff --git a/src/main/java/net/sytes/kashey/sweater/config/MvcConfig.java b/src/main/java/net/sytes/kashey/sweater/config/MvcConfig.java
new file mode 100644
index 0000000..cb1eb8f
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/config/MvcConfig.java
@@ -0,0 +1,13 @@
+package net.sytes.kashey.sweater.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class MvcConfig implements WebMvcConfigurer {
+ public void addViewControllers(ViewControllerRegistry registry) {
+
+ registry.addViewController("/login").setViewName("login");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/sytes/kashey/sweater/config/WebSecurityConfig.java b/src/main/java/net/sytes/kashey/sweater/config/WebSecurityConfig.java
new file mode 100644
index 0000000..00f9ffd
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/config/WebSecurityConfig.java
@@ -0,0 +1,34 @@
+package net.sytes.kashey.sweater.config;
+
+import net.sytes.kashey.sweater.service.CustomUserDetailsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig {
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .authorizeHttpRequests((requests) -> requests
+ .requestMatchers("/", "/registration").permitAll()
+ .anyRequest().authenticated()
+ )
+ .formLogin((form) -> form
+ .loginPage("/login")
+ .defaultSuccessUrl("/main", true)
+ .permitAll()
+ )
+ .logout(LogoutConfigurer::permitAll);
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/sytes/kashey/sweater/GreetingController.java b/src/main/java/net/sytes/kashey/sweater/controller/MainController.java
similarity index 76%
rename from src/main/java/net/sytes/kashey/sweater/GreetingController.java
rename to src/main/java/net/sytes/kashey/sweater/controller/MainController.java
index 31e0589..8a095c9 100644
--- a/src/main/java/net/sytes/kashey/sweater/GreetingController.java
+++ b/src/main/java/net/sytes/kashey/sweater/controller/MainController.java
@@ -1,4 +1,4 @@
-package net.sytes.kashey.sweater;
+package net.sytes.kashey.sweater.controller;
import net.sytes.kashey.sweater.domain.Message;
import net.sytes.kashey.sweater.repository.MessageRepository;
@@ -10,29 +10,27 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@Controller
-public class GreetingController {
+public class MainController {
private final MessageRepository messageRepository;
- public GreetingController(MessageRepository messageRepository) {
+ public MainController(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
- @GetMapping("/greeting")
- public String greeting(@RequestParam(name = "name", required = false, defaultValue = "World") String name,
- Map model) {
- model.put("name", name);
+ @GetMapping("/")
+ public String greeting() {
return "greeting";
}
- @GetMapping
+ @GetMapping("/main")
public String findAllMessages(Map model) {
model.put("messages", messageRepository.findAll());
return "main";
}
- @PostMapping
+ @PostMapping("/main")
public String addMessage(@RequestParam String text, @RequestParam String tag, Map model) {
messageRepository.save(new Message(text, tag));
diff --git a/src/main/java/net/sytes/kashey/sweater/controller/RegistrationController.java b/src/main/java/net/sytes/kashey/sweater/controller/RegistrationController.java
new file mode 100644
index 0000000..7c2e4fd
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/controller/RegistrationController.java
@@ -0,0 +1,48 @@
+package net.sytes.kashey.sweater.controller;
+
+import net.sytes.kashey.sweater.domain.Role;
+import net.sytes.kashey.sweater.domain.User;
+import net.sytes.kashey.sweater.repository.UserRepository;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Controller
+public class RegistrationController {
+
+ private final UserRepository userRepository;
+
+ public RegistrationController(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @GetMapping("/registration")
+ public String registration() {
+
+ return "registration";
+ }
+
+ @PostMapping("/registration")
+ public String addUser(@ModelAttribute User user, Map model) {
+
+ if (user.getUsername() == null || user.getUsername().isEmpty() || user.getUsername().isBlank()) {
+ model.put("message", "User name must not be empty");
+ return "registration";
+ }
+
+ if (userRepository.findByUsername(user.getUsername()).isPresent()) {
+ model.put("message", "User already exists");
+ return "registration";
+ }
+
+ user.setEnabled(true);
+ user.setRoles(Collections.singleton(Role.USER));
+ user.setPassword("{noop}" + user.getPassword()); //пока не введено шифрование паролей
+ userRepository.save(user);
+ return "redirect:/login";
+ }
+}
diff --git a/src/main/java/net/sytes/kashey/sweater/domain/Role.java b/src/main/java/net/sytes/kashey/sweater/domain/Role.java
new file mode 100644
index 0000000..9d58522
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/domain/Role.java
@@ -0,0 +1,5 @@
+package net.sytes.kashey.sweater.domain;
+
+public enum Role {
+ ADMIN, USER;
+}
diff --git a/src/main/java/net/sytes/kashey/sweater/domain/User.java b/src/main/java/net/sytes/kashey/sweater/domain/User.java
new file mode 100644
index 0000000..368517d
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/domain/User.java
@@ -0,0 +1,73 @@
+package net.sytes.kashey.sweater.domain;
+
+import jakarta.persistence.*;
+
+import java.util.Set;
+
+@Entity
+@Table(name = "usr")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ private String username;
+ private String password;
+ private boolean enabled;
+ @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
+ @CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
+ @Enumerated(EnumType.STRING)
+ private Set roles;
+
+ public User() {
+ }
+
+ public User(String username) {
+ this.username = username;
+ }
+
+ public User(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String name) {
+ this.username = name;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Set roles) {
+ this.roles = roles;
+ }
+}
diff --git a/src/main/java/net/sytes/kashey/sweater/repository/UserRepository.java b/src/main/java/net/sytes/kashey/sweater/repository/UserRepository.java
new file mode 100644
index 0000000..44e2231
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/repository/UserRepository.java
@@ -0,0 +1,12 @@
+package net.sytes.kashey.sweater.repository;
+
+import net.sytes.kashey.sweater.domain.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+ Optional findByUsername(String name);
+}
diff --git a/src/main/java/net/sytes/kashey/sweater/security/CustomUserDetails.java b/src/main/java/net/sytes/kashey/sweater/security/CustomUserDetails.java
new file mode 100644
index 0000000..52c59a5
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/security/CustomUserDetails.java
@@ -0,0 +1,55 @@
+package net.sytes.kashey.sweater.security;
+
+import net.sytes.kashey.sweater.domain.User;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class CustomUserDetails implements UserDetails {
+
+ private final User user;
+
+ public CustomUserDetails(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return user.getRoles().stream()
+ .map(role -> new SimpleGrantedAuthority(role.name()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return user.isEnabled();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/sytes/kashey/sweater/service/CustomUserDetailsService.java b/src/main/java/net/sytes/kashey/sweater/service/CustomUserDetailsService.java
new file mode 100644
index 0000000..4fcf513
--- /dev/null
+++ b/src/main/java/net/sytes/kashey/sweater/service/CustomUserDetailsService.java
@@ -0,0 +1,28 @@
+package net.sytes.kashey.sweater.service;
+
+import net.sytes.kashey.sweater.domain.User;
+import net.sytes.kashey.sweater.repository.UserRepository;
+import net.sytes.kashey.sweater.security.CustomUserDetails;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CustomUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ public CustomUserDetailsService(UserRepository userRepository) {
+
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
+
+ User user = userRepository.findByUsername(name)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
+ return new CustomUserDetails(user);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 650d561..b47762d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -3,4 +3,5 @@ spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/sweater_db
spring.datasource.username=user
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.mustache.servlet.expose-request-attributes=true
diff --git a/src/main/resources/templates/greeting.mustache b/src/main/resources/templates/greeting.mustache
index 1d5b344..679bd63 100644
--- a/src/main/resources/templates/greeting.mustache
+++ b/src/main/resources/templates/greeting.mustache
@@ -5,6 +5,7 @@
-Hello {{name}}!!!
+Hello there
+Main page