diff --git a/build.gradle b/build.gradle index e12cdfb..e47d4d8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,21 +6,28 @@ plugins { group = 'ru.ulstu.is' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' +sourceCompatibility = '19' repositories { mavenCentral() } dependencies { + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'com.h2database:h2:2.1.210' + implementation 'com.auth0:java-jwt:4.4.0' + //implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' implementation 'org.webjars:bootstrap:5.1.3' implementation 'org.webjars:jquery:3.6.0' implementation 'org.webjars:font-awesome:6.1.0' + implementation 'jakarta.validation:jakarta.validation-api:3.0.0' implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5' diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java new file mode 100644 index 0000000..bed9acf --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java @@ -0,0 +1,29 @@ +/* +package ru.ulstu.is.sbapp.HardwareShop.controller; + +import ru.ip.labworks.labworks.bookshop.model.User; +import ru.ip.labworks.labworks.bookshop.model.UserRole; + +public class UserDto { + private final long id; + private final String login; + private final UserRole role; + + public UserDto(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.role = user.getRole(); + } + + public long getId() { + return id; + } + + public String getLogin() { + return login; + } + + public UserRole getRole() { + return role; + } +}*/ diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java new file mode 100644 index 0000000..65ce268 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java @@ -0,0 +1,84 @@ +/* +package ru.ulstu.is.sbapp.HardwareShop.models; + +import jakarta.persistance.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import java.util.Objects; + +@Entity +@Table(name = "users") +public class User { + @Id + @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 User() { + } + + public User(String login, String password) { + this(login, password, UserRole.USER); + } + + public User(String login, String password, UserRole role) { + this.login = login; + this.password = password; + this.role = role; + } + + public Long getId() { + return id; + } + + 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 UserRole getRole() { + return role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(login, user.login); + } + + @Override + public int hashCode() { + return Objects.hash(id, login); + } + @Override + public String toString() { + return "User{" + + "id=" + id + + ", login='" + login + '\'' + + ", password='" + password + '\'' + + ", role='" + role + '\'' + + '}'; + } +}*/ diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java new file mode 100644 index 0000000..174da83 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java @@ -0,0 +1,20 @@ +package ru.ulstu.is.sbapp.HardwareShop.models; + +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"; + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java new file mode 100644 index 0000000..1857a3b --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java @@ -0,0 +1,9 @@ +/* +package ru.ulstu.is.sbapp.HardwareShop.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.is.sbapp.HardwareShop.models.User; + +public interface UserRepository extends JpaRepository { + User findOneByLoginIgnoreCase(String login); +}*/ diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java new file mode 100644 index 0000000..a6b39ab --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.HardwareShop.services; + +public class UserNotFoundException extends RuntimeException{ + public UserNotFoundException(Long id) { + super(String.format("User with id [%s] is not found", id)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java new file mode 100644 index 0000000..c666ee7 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java @@ -0,0 +1,67 @@ +/* +package ru.ulstu.is.sbapp.HardwareShop.services; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +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.transaction.annotation.Transactional; +import ru.ulstu.is.sbapp.HardwareShop.models.User; +import ru.ulstu.is.sbapp.HardwareShop.models.UserRole; +import ru.ulstu.is.sbapp.HardwareShop.repository.UserRepository; + +import jakarta.validation.ValidationException; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; + +@Service +public class UserService implements UserDetailsService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + public UserService(UserRepository userRepository, + PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + public Page findAllPages(int page, int size) { + return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending())); + } + public User findByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + public User createUser(String login, String password, String passwordConfirm) { + return createUser(login, password, passwordConfirm, UserRole.USER); + } + public User createUser(String login, String password, String passwordConfirm, UserRole role) { + if (findByLogin(login) != null) { + throw new ValidationException(String.format("User '%s' already exists", login)); + } + final User user = new User(login, passwordEncoder.encode(password), role); + if (!Objects.equals(password, passwordConfirm)) { + throw new ValidationException("Passwords not equals"); + } + return userRepository.save(user); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User 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())); + } + + @Transactional(readOnly = true) + public User findUser(Long id) { + final Optional user = userRepository.findById(id); + return user.orElseThrow(() -> new UserNotFoundException(id)); + } +}*/ diff --git a/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..e5540d0 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package ru.ulstu.is.sbapp; + +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(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java new file mode 100644 index 0000000..2dd43ad --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java @@ -0,0 +1,68 @@ +/* +package ru.ulstu.is.sbapp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +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.configuration.WebSecurityCustomizer; +import org.springframework.security.web.SecurityFilterChain; +import ru.ip.labworks.labworks.bookshop.controller.UserSignUpMvcController; +import ru.ip.labworks.labworks.bookshop.model.UserRole; +import ru.ip.labworks.labworks.bookshop.service.UserService; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity( + securedEnabled = true +) +public class SecurityConfiguration { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static final String LOGIN_URL = "/login"; + private final UserService userService; + + public SecurityConfiguration(UserService userService) { + this.userService = userService; + createAdminOnStartup(); + } + + private void createAdminOnStartup() { + final String admin = "admin"; + if (userService.findByLogin(admin) == null) { + log.info("Admin user successfully created"); + userService.createUser(admin, admin, admin, UserRole.ADMIN); + } + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.headers().frameOptions().sameOrigin().and() + .cors().and() + .csrf().disable() + .authorizeHttpRequests() + .requestMatchers(UserSignUpMvcController.SIGNUP_URL).permitAll() + .requestMatchers(HttpMethod.GET, LOGIN_URL).permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage(LOGIN_URL).permitAll() + .defaultSuccessUrl("/author", true) + .and() + .logout().permitAll(); + return http.userDetailsService(userService).build(); + } + + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring() + .requestMatchers("/css/**") + .requestMatchers("/js/**") + .requestMatchers("/templates/**") + .requestMatchers("/webjars/**"); + } +}*/ diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..e8b17ca --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,42 @@ + + + +
+
+ User not found +
+
+ Logout success +
+
+ User '' was successfully created +
+
+
+

Login

+ +
+
+

Password

+ +
+
+ +
+
+

+ Not a member yet? + Sign Up here +

+
+
+
+ + \ 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..9f4e0e6 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,29 @@ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+ + Назад +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/users.html b/src/main/resources/templates/users.html new file mode 100644 index 0000000..40b7f21 --- /dev/null +++ b/src/main/resources/templates/users.html @@ -0,0 +1,38 @@ + + + +
+
+ + + + + + + + + + + + + + + + + +
#IDЛогинРоль
+
+ +
+ + \ No newline at end of file