2 Commits
main ... lab6_7

Author SHA1 Message Date
9339a1b7b1 lab6-7 2024-12-09 13:39:53 +03:00
baeeed40d1 lab5 2024-11-14 13:35:46 +03:00
20 changed files with 376 additions and 47 deletions

View File

@@ -28,12 +28,19 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.security:spring-security-core'
implementation 'org.springframework.security:spring-security-web'
implementation 'org.springframework.security:spring-security-config'
implementation 'org.springframework.security:spring-security-crypto'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.hibernate.orm:hibernate-community-dialects' implementation 'org.hibernate.orm:hibernate-community-dialects'
implementation 'org.xerial:sqlite-jdbc:3.46.1.3' implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
implementation 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'

View File

@@ -0,0 +1,59 @@
package ru.lionarius.isdojplab.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/").permitAll()
.requestMatchers("/login").permitAll()
.requestMatchers("/register").permitAll()
.requestMatchers("/logout").permitAll()
// .requestMatchers("/api/**").permitAll()
.requestMatchers("/css/**").permitAll()
.requestMatchers("/js/**").permitAll()
.anyRequest().authenticated()
);
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));
http.logout(logout -> logout.logoutSuccessUrl("/").permitAll());
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
.securityContext(securityContext -> securityContext
.securityContextRepository(new HttpSessionSecurityContextRepository())
);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
var provider = new DaoAuthenticationProvider(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return new ProviderManager(provider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,13 @@
package ru.lionarius.isdojplab.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "store")
public class StoreProperties {
private String name = "Магазин электротоваров";
private String description = "Все для вашего дома и офиса";
}

View File

@@ -12,6 +12,9 @@ public class CartController {
@GetMapping @GetMapping
public String showCart(Model model) { public String showCart(Model model) {
var cart = new Cart();
model.addAttribute("cart", cart);
return "cart"; return "cart";
} }
} }

View File

@@ -4,22 +4,22 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import ru.lionarius.isdojplab.model.Product;
import ru.lionarius.isdojplab.repository.ProductRepository; import ru.lionarius.isdojplab.repository.ProductRepository;
import java.util.List;
@Controller @Controller
public class HomeController { public class HomeController {
@Autowired private final ProductRepository productRepository;
private ProductRepository productRepository;
public HomeController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@GetMapping("/") @GetMapping("/")
public String home(Model model) { public String home(Model model) {
model.addAttribute("message", "Добро пожаловать в магазин электротоваров!"); model.addAttribute("message", "Добро пожаловать в магазин электротоваров!");
List<Product> products = productRepository.findAll(); var products = productRepository.findAll();
model.addAttribute("products", products); model.addAttribute("products", products);

View File

@@ -0,0 +1,74 @@
package ru.lionarius.isdojplab.controller;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
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 ru.lionarius.isdojplab.model.User;
import ru.lionarius.isdojplab.repository.UserRepository;
@Controller
public class LoginFormController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
public LoginFormController(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
}
@GetMapping("/login")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "login_form";
}
@PostMapping("/login")
public String submitForm(@ModelAttribute("user") User user, BindingResult bindingResult, Model model, HttpSession httpSession) {
if (bindingResult.hasErrors()) {
return "login_form";
}
var userFromDb = userRepository.findByEmail(user.getEmail());
if (userFromDb.isEmpty()) {
bindingResult.rejectValue("email", "email.not.found", "Пользователь не найден");
return "login_form";
}
if (!passwordEncoder.matches(user.getPassword(), userFromDb.get().getPassword())) {
bindingResult.rejectValue("password", "password.incorrect", "Неверный пароль");
return "login_form";
}
model.addAttribute("user", userFromDb.get());
var authCredentials = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
var auth = authenticationManager.authenticate(authCredentials);
var context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
httpSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
return "redirect:/";
}
@GetMapping("/logout")
public String logout(HttpSession httpSession) {
SecurityContextHolder.clearContext();
httpSession.invalidate();
return "redirect:/";
}
}

View File

@@ -1,7 +1,7 @@
package ru.lionarius.isdojplab.controller; package ru.lionarius.isdojplab.controller;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
@@ -14,8 +14,13 @@ import ru.lionarius.isdojplab.repository.UserRepository;
@Controller @Controller
public class RegisterFormController { public class RegisterFormController {
@Autowired private final UserRepository userRepository;
private UserRepository userRepository; private final PasswordEncoder passwordEncoder;
public RegisterFormController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@GetMapping("/register") @GetMapping("/register")
public String showForm(Model model) { public String showForm(Model model) {
@@ -40,10 +45,13 @@ public class RegisterFormController {
return "register_form"; return "register_form";
} }
var hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
userRepository.save(user); userRepository.save(user);
model.addAttribute("message", "Пользователь успешно зарегистрирован"); model.addAttribute("message", "Пользователь успешно зарегистрирован");
return "register_form"; return "redirect:/login";
} }
} }

View File

@@ -1,6 +1,9 @@
package ru.lionarius.isdojplab.model; package ru.lionarius.isdojplab.model;
import jakarta.persistence.*; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;

View File

@@ -8,10 +8,16 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
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.List;
@Data @Data
@Entity @Entity
public class User { public class User implements UserDetails {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@@ -26,4 +32,14 @@ public class User {
@NotBlank(message = "Поле не может быть пустым") @NotBlank(message = "Поле не может быть пустым")
@Size(min = 6, message = "Пароль должен состоять не менее 6 символов") @Size(min = 6, message = "Пароль должен состоять не менее 6 символов")
private String password; private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("SIMPLE_USER"));
}
@Override
public String getUsername() {
return email;
}
} }

View File

@@ -1,9 +1,9 @@
package ru.lionarius.isdojplab.repository; package ru.lionarius.isdojplab.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.lionarius.isdojplab.model.Product; import ru.lionarius.isdojplab.model.Product;
@Repository @Repository
public interface ProductRepository extends JpaRepository<Product, Long> { public interface ProductRepository extends CrudRepository<Product, Long> {
} }

View File

@@ -1,14 +1,15 @@
package ru.lionarius.isdojplab.repository; package ru.lionarius.isdojplab.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.lionarius.isdojplab.model.User; import ru.lionarius.isdojplab.model.User;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
Optional<User> findByName(String name); Optional<User> findByName(String name);
} }

View File

@@ -0,0 +1,35 @@
package ru.lionarius.isdojplab.rest;
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import ru.lionarius.isdojplab.model.User;
@Component
@RepositoryEventHandler
public class UserEventHandler {
private final PasswordEncoder passwordEncoder;
public UserEventHandler(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@HandleBeforeCreate
public void handleBeforeCreate(User user) {
encodePassword(user);
}
@HandleBeforeSave
public void handleBeforeSave(User user) {
encodePassword(user);
}
private void encodePassword(User user) {
if (user.getPassword() != null) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
}
}

View File

@@ -0,0 +1,25 @@
package ru.lionarius.isdojplab.runner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import ru.lionarius.isdojplab.model.Product;
import ru.lionarius.isdojplab.repository.ProductRepository;
@Component
@Profile("dev")
public class DevDataInitializer implements CommandLineRunner {
private final ProductRepository productRepository;
public DevDataInitializer(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Override
public void run(String... args) throws Exception {
productRepository.save(new Product(null, "Тестовый товар 1", 1000.0, "Описание товара", null));
productRepository.save(new Product(null, "Тестовый товар 2", 2000.0, "Описание товара", null));
productRepository.save(new Product(null, "Тестовый товар 3", 3000.0, "Описание товара", null));
}
}

View File

@@ -0,0 +1,22 @@
package ru.lionarius.isdojplab.service;
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;
import ru.lionarius.isdojplab.repository.UserRepository;
@Service
public class MyUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public MyUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(email));
}
}

View File

@@ -0,0 +1,6 @@
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.url=jdbc:sqlite::memory:
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

View File

@@ -0,0 +1,6 @@
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.url=jdbc:sqlite:./database/data.db
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false

View File

@@ -1,7 +1,6 @@
spring.profiles.active=dev
spring.application.name=isdojp-lab spring.application.name=isdojp-lab
spring.datasource.driver-class-name=org.sqlite.JDBC spring.data.rest.base-path=/api
spring.datasource.url=jdbc:sqlite:./database/data.db
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect store.name=asdfasfsdfs
spring.jpa.generate-ddl=true store.description=\u0412\u0441\u0435 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u0434\u043e\u043c\u0430 \u0438 \u043e\u0444\u0438\u0441\u0430
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -9,13 +9,21 @@
<body> <body>
<header> <header>
<h1>Магазин электротоваров</h1> <h1 th:text="${@storeProperties.name}"></h1>
<p>Все для вашего дома и офиса</p> <p th:text="${@storeProperties.description}"></p>
</header> </header>
<div class="container"> <div class="container">
<div class="register-container"> <div sec:authorize="!isAuthenticated()">
<a href="/register" class="register-button">Регистрация</a> <div class="register-container">
<a href="/register" class="register-button">Регистрация</a>
</div>
</div>
<div sec:authorize="isAuthenticated()">
<div class="register-container">
<a href="/logout" class="register-button">Выход</a>
</div>
</div> </div>
<h2>Каталог товаров</h2> <h2>Каталог товаров</h2>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Авторизация</title>
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<header>
<h1>Авторизация пользователя</h1>
</header>
<div class="container">
<form th:action="@{/login}" th:object="${user}" method="post">
<div>
<label for="email">Email:</label>
<input type="email" id="email" th:field="*{email}" />
<br />
<span style="color: red" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
</div>
<div>
<label for="password">Пароль:</label>
<input type="password" id="password" th:field="*{password}" />
<br />
<span style="color: red" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>
</div>
<button type="submit">Авторизироваться</button>
</form>
<p th:if="${message}" th:text="${message}"></p>
</div>
<footer>
<p>© 2024 Магазин электротоваров. Все права защищены.</p>
</footer>
</body>
</html>

View File

@@ -39,8 +39,10 @@
</form> </form>
<p th:if="${message}" th:text="${message}"></p> <p th:if="${message}" th:text="${message}"></p>
<a href="/login">Уже зарегистрированы?</a>
</div> </div>
<footer> <footer>
<p>© 2024 Магазин электротоваров. Все права защищены.</p> <p>© 2024 Магазин электротоваров. Все права защищены.</p>
</footer> </footer>