diff --git a/pom.xml b/pom.xml index 7a4a000..4c7f6ec 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,73 @@ HedgeHogCloud 1.0-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 2.6.2 + + 17 17 + 17 + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + + + mysql + mysql-connector-java + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.projectlombok + lombok + 1.18.26 + provided + + + + org.liquibase + liquibase-core + 4.19.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${project.parent.version} + + + + \ No newline at end of file diff --git a/src/main/java/ru/ldeloff/Main.java b/src/main/java/ru/ldeloff/Main.java deleted file mode 100644 index 7620479..0000000 --- a/src/main/java/ru/ldeloff/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.ldeloff; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - } -} \ No newline at end of file diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/Main.java b/src/main/java/ru/ldeloff/hedgehogcloud/Main.java new file mode 100644 index 0000000..682f8e1 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/Main.java @@ -0,0 +1,11 @@ +package ru.ldeloff.hedgehogcloud; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class,args); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/config/AuthenticationSuccessUserHandler.java b/src/main/java/ru/ldeloff/hedgehogcloud/config/AuthenticationSuccessUserHandler.java new file mode 100644 index 0000000..74e28cd --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/config/AuthenticationSuccessUserHandler.java @@ -0,0 +1,25 @@ +package ru.ldeloff.hedgehogcloud.config; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +@Component +public class AuthenticationSuccessUserHandler implements AuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + Set roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); + if (roles.contains("ROLE_ADMIN")) { + response.sendRedirect("/admin"); + } else { + response.sendRedirect("/user"); + } + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/config/MvcConfig.java b/src/main/java/ru/ldeloff/hedgehogcloud/config/MvcConfig.java new file mode 100644 index 0000000..8160a3c --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/config/MvcConfig.java @@ -0,0 +1,13 @@ +package ru.ldeloff.hedgehogcloud.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 { + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/config/WebSecurityConfig.java b/src/main/java/ru/ldeloff/hedgehogcloud/config/WebSecurityConfig.java new file mode 100644 index 0000000..83fa1b4 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/config/WebSecurityConfig.java @@ -0,0 +1,61 @@ +package ru.ldeloff.hedgehogcloud.config; + +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.authentication.builders.AuthenticationManagerBuilder; +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.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import ru.ldeloff.hedgehogcloud.service.UserServiceImpl; + + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + UserServiceImpl userService; + AuthenticationSuccessUserHandler authenticationSuccessUserHandler; + + public WebSecurityConfig(UserServiceImpl userService, AuthenticationSuccessUserHandler authenticationSuccessUserHandler) { + this.userService = userService; + this.authenticationSuccessUserHandler = authenticationSuccessUserHandler; + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.formLogin() + // указываем страницу с формой логина + //.loginPage("/login") + //указываем логику обработки при логине + .successHandler(authenticationSuccessUserHandler) + // указываем action с формы логина + .loginProcessingUrl("/login") + // указываем параметры логина и пароля с формы логина + .usernameParameter("j_username") + .passwordParameter("j_password") + // даем доступ к форме логина всем + .permitAll(); + http + // делаем страницу регистрации недоступной для авторизированных пользователей + .authorizeRequests() + //страницы аутентификации доступна всем + .antMatchers("/login").anonymous() + .antMatchers("/").authenticated() + // защищенные URL + .antMatchers("/admin/**").access("hasAnyRole('ROLE_ADMIN')") + .antMatchers("/user").permitAll() + .and().formLogin(); + } + + @Autowired + protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userService).passwordEncoder(NoOpPasswordEncoder.getInstance()); + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/config/initclass/InitDB.java b/src/main/java/ru/ldeloff/hedgehogcloud/config/initclass/InitDB.java new file mode 100644 index 0000000..f1e6df2 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/config/initclass/InitDB.java @@ -0,0 +1,49 @@ +package ru.ldeloff.hedgehogcloud.config.initclass; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import ru.ldeloff.hedgehogcloud.entity.RoleEntity; +import ru.ldeloff.hedgehogcloud.entity.UserEntity; +import ru.ldeloff.hedgehogcloud.service.RoleService; +import ru.ldeloff.hedgehogcloud.service.UserService; + +import java.util.HashSet; +import java.util.List; + +@Component +public class InitDB implements ApplicationRunner { + UserService userService; + RoleService roleService; + + public InitDB(UserService userService, RoleService roleService) { + this.userService = userService; + this.roleService = roleService; + } + + @Override + @Transactional + public void run(ApplicationArguments args) throws Exception { + RoleEntity role = new RoleEntity(); + role.setName("ROLE_ADMIN"); + roleService.saveRole(role); + + RoleEntity role2 = new RoleEntity(); + role2.setName("ROLE_USER"); + roleService.saveRole(role2); + + UserEntity user = new UserEntity(); + user.setUsername("admin"); + user.setPassword("123"); + user.setRoles(new HashSet<>(List.of(roleService.getByName(role.getName())))); + userService.saveUser(user); + + UserEntity user2 = new UserEntity(); + user2.setUsername("user"); + user2.setPassword("123"); + user2.setRoles(new HashSet<>(List.of(roleService.getByName(role2.getName())))); + userService.saveUser(user2); + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/controller/LoginController.java b/src/main/java/ru/ldeloff/hedgehogcloud/controller/LoginController.java new file mode 100644 index 0000000..ba5629c --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/controller/LoginController.java @@ -0,0 +1,13 @@ +package ru.ldeloff.hedgehogcloud.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class LoginController { + + @GetMapping(value = "/login") + public String loginPage() { + return "login"; + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/entity/BaseEntity.java b/src/main/java/ru/ldeloff/hedgehogcloud/entity/BaseEntity.java new file mode 100644 index 0000000..fb79963 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/entity/BaseEntity.java @@ -0,0 +1,33 @@ +package ru.ldeloff.hedgehogcloud.entity; + +import com.sun.istack.NotNull; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.Persistable; + +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import java.util.UUID; + +@NoArgsConstructor +@Setter +@MappedSuperclass +@EqualsAndHashCode +public class BaseEntity implements Persistable { + @Id + protected String id = UUID.randomUUID().toString(); + @Transient + private Boolean justCreated = false; + + @Override + public String getId() { + return null; + } + + @Override + public boolean isNew() { + return justCreated; + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/entity/RoleEntity.java b/src/main/java/ru/ldeloff/hedgehogcloud/entity/RoleEntity.java new file mode 100644 index 0000000..59598f5 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/entity/RoleEntity.java @@ -0,0 +1,30 @@ +package ru.ldeloff.hedgehogcloud.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.*; +import java.util.Set; + +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "roles") +public class RoleEntity extends BaseEntity implements GrantedAuthority { + private String name; + @Transient + @ManyToMany + private Set users; + + public String getName() { + return name; + } + @Override + public String getAuthority() { + return getName(); + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/entity/UserEntity.java b/src/main/java/ru/ldeloff/hedgehogcloud/entity/UserEntity.java new file mode 100644 index 0000000..469491f --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/entity/UserEntity.java @@ -0,0 +1,63 @@ +package ru.ldeloff.hedgehogcloud.entity; + +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "users") +public class UserEntity extends BaseEntity implements UserDetails { + private String username; + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable( + name = "users_roles", + joinColumns = { @JoinColumn(name = "user_id") }, + inverseJoinColumns = { @JoinColumn(name = "role_id") } + ) + private Set roles = new HashSet<>(); + + @Override + public Collection getAuthorities() { + return getRoles(); + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/repository/RoleRepository.java b/src/main/java/ru/ldeloff/hedgehogcloud/repository/RoleRepository.java new file mode 100644 index 0000000..60d5e7a --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/repository/RoleRepository.java @@ -0,0 +1,10 @@ +package ru.ldeloff.hedgehogcloud.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.ldeloff.hedgehogcloud.entity.RoleEntity; + +@Repository +public interface RoleRepository extends JpaRepository { + RoleEntity findByName(String name); +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/repository/UserRepository.java b/src/main/java/ru/ldeloff/hedgehogcloud/repository/UserRepository.java new file mode 100644 index 0000000..9aedbf1 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/repository/UserRepository.java @@ -0,0 +1,11 @@ +package ru.ldeloff.hedgehogcloud.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.ldeloff.hedgehogcloud.entity.UserEntity; + +@Repository +public interface UserRepository extends JpaRepository { + UserEntity findByUsername(String username); + +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleService.java b/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleService.java new file mode 100644 index 0000000..8efb05c --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleService.java @@ -0,0 +1,8 @@ +package ru.ldeloff.hedgehogcloud.service; + +import ru.ldeloff.hedgehogcloud.entity.RoleEntity; + +public interface RoleService { + void saveRole(RoleEntity roleEntity); + RoleEntity getByName(String name); +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleServiceImpl.java b/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleServiceImpl.java new file mode 100644 index 0000000..5e08e12 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/service/RoleServiceImpl.java @@ -0,0 +1,31 @@ +package ru.ldeloff.hedgehogcloud.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import ru.ldeloff.hedgehogcloud.entity.RoleEntity; +import ru.ldeloff.hedgehogcloud.entity.UserEntity; +import ru.ldeloff.hedgehogcloud.repository.RoleRepository; + +@Service +public class RoleServiceImpl implements RoleService { + RoleRepository roleRepository; + + public RoleServiceImpl(RoleRepository roleRepository) { + this.roleRepository = roleRepository; + } + + + public void saveRole(RoleEntity role) { + RoleEntity roleTemp = getByName(role.getName()); + if (roleTemp == null) { + roleRepository.save(role); + } + } + + @Override + public RoleEntity getByName(String name) { + return roleRepository.findByName(name); + } + +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/service/UserService.java b/src/main/java/ru/ldeloff/hedgehogcloud/service/UserService.java new file mode 100644 index 0000000..1fb3c71 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/service/UserService.java @@ -0,0 +1,11 @@ +package ru.ldeloff.hedgehogcloud.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import ru.ldeloff.hedgehogcloud.entity.UserEntity; + +public interface UserService extends UserDetailsService { + UserDetails loadUserByUsername(String username); + void saveUser(UserEntity user); + UserEntity getByUserName(String name); // TODO попробовать userdetails юзать +} diff --git a/src/main/java/ru/ldeloff/hedgehogcloud/service/UserServiceImpl.java b/src/main/java/ru/ldeloff/hedgehogcloud/service/UserServiceImpl.java new file mode 100644 index 0000000..9da1c73 --- /dev/null +++ b/src/main/java/ru/ldeloff/hedgehogcloud/service/UserServiceImpl.java @@ -0,0 +1,46 @@ +package ru.ldeloff.hedgehogcloud.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import ru.ldeloff.hedgehogcloud.entity.UserEntity; +import ru.ldeloff.hedgehogcloud.repository.RoleRepository; +import ru.ldeloff.hedgehogcloud.repository.UserRepository; + +@Service +public class UserServiceImpl implements UserService { + UserRepository userRepository; + RoleRepository roleRepository; + + @Autowired + public UserServiceImpl(UserRepository userRepository, + RoleRepository roleRepository) { + this.userRepository = userRepository; + this.roleRepository = roleRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + UserEntity user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("User not found"); + } + return user; + } + + @Override + public void saveUser(UserEntity user) { + UserEntity userTemp = getByUserName(user.getUsername()); + if (userTemp == null) { + userRepository.save(user); + } + } + + @Override + public UserEntity getByUserName(String name) { + return userRepository.findByUsername(name); + } + + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c2e5d26 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/hedgehogcloud?verifyServerCertificate=false&useSSL=false&requireSSL=false&useLegacyDatetimeCode=false&&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: root + password: 123 + liquibase: + enabled: true + change-log: classpath:db/scripts/changelog-master.xml + url: jdbc:mysql://localhost:3306/hedgehogcloud?verifyServerCertificate=false&useSSL=false&requireSSL=false&useLegacyDatetimeCode=false&&serverTimezone=UTC&allowPublicKeyRetrieval=true + user: root + password: 123 + liquibase-schema: "liquibase" + default-schema: "hedgehogcloud" diff --git a/src/main/resources/db/scripts/changelog-master.xml b/src/main/resources/db/scripts/changelog-master.xml new file mode 100644 index 0000000..1614c28 --- /dev/null +++ b/src/main/resources/db/scripts/changelog-master.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/scripts/release_0_0_1/changelog.xml b/src/main/resources/db/scripts/release_0_0_1/changelog.xml new file mode 100644 index 0000000..4ce4477 --- /dev/null +++ b/src/main/resources/db/scripts/release_0_0_1/changelog.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/scripts/release_0_0_1/scripts/01_create_table_users.sql b/src/main/resources/db/scripts/release_0_0_1/scripts/01_create_table_users.sql new file mode 100644 index 0000000..c1b9e5a --- /dev/null +++ b/src/main/resources/db/scripts/release_0_0_1/scripts/01_create_table_users.sql @@ -0,0 +1,13 @@ +-- liquibase formatted sql + +-- changeset L_DelOff:create_table_users rollbackSplitStatements:true +-- comment: Создание таблицы с пользователями + +CREATE TABLE USERS +( + ID VARCHAR(36) PRIMARY KEY COMMENT 'Идентификатор', + USERNAME VARCHAR(64) UNIQUE COMMENT 'Имя пользователя', + PASSWORD VARCHAR(36) COMMENT 'Пароль' +); + +-- rollback DROP TABLE USERS; \ No newline at end of file diff --git a/src/main/resources/db/scripts/release_0_0_1/scripts/02_create_table_roles.sql b/src/main/resources/db/scripts/release_0_0_1/scripts/02_create_table_roles.sql new file mode 100644 index 0000000..0bee998 --- /dev/null +++ b/src/main/resources/db/scripts/release_0_0_1/scripts/02_create_table_roles.sql @@ -0,0 +1,13 @@ +-- liquibase formatted sql + +-- changeset L_DelOff:create_table_roles rollbackSplitStatements:true +-- comment: Создание таблицы с ролями + +CREATE TABLE ROLES +( + ID VARCHAR(36) PRIMARY KEY COMMENT 'Идентификатор роли', + NAME VARCHAR(64) UNIQUE COMMENT 'Имя роли' +); + + +-- rollback DROP TABLE ROLES; \ No newline at end of file diff --git a/src/main/resources/db/scripts/release_0_0_1/scripts/03_create_table_users_roles.sql b/src/main/resources/db/scripts/release_0_0_1/scripts/03_create_table_users_roles.sql new file mode 100644 index 0000000..0e6a177 --- /dev/null +++ b/src/main/resources/db/scripts/release_0_0_1/scripts/03_create_table_users_roles.sql @@ -0,0 +1,19 @@ +-- liquibase formatted sql + +-- changeset L_DelOff:create_table_users_roles rollbackSplitStatements:true +-- comment: Создание сопоставляющей таблицы пользователь - роли + +CREATE TABLE users_roles +( + `user_id` VARCHAR(36) NOT NULL, + `role_id` VARCHAR(36) NOT NULL, + PRIMARY KEY (`user_id`,`role_id`), + KEY `role_id` (`role_id`), + CONSTRAINT `users_roles_ibfk_1` + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `users_roles_ibfk_2` + FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) +); + + +-- rollback DROP TABLE users_roles; \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..a69d21c --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,26 @@ + + + + + + Login Page + + + +
+
+
+
+
+

Please sign in

+ + + +
+
+
+
+
+ + + \ No newline at end of file