This commit is contained in:
2024-11-14 13:35:46 +03:00
parent 31d2c3e804
commit baeeed40d1
14 changed files with 286 additions and 36 deletions

View File

@@ -29,6 +29,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
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'

View File

@@ -0,0 +1,61 @@
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.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.authorizeHttpRequests(auth -> auth
.requestMatchers("/").permitAll()
.requestMatchers("/login").permitAll()
.requestMatchers("/register").permitAll()
.requestMatchers("/logout").permitAll()
.requestMatchers("/css/**").permitAll()
.requestMatchers("/js/**").permitAll()
.anyRequest().authenticated()
);
// http.formLogin(form -> form
// .loginPage("/login")
// .loginProcessingUrl("/login")
// .usernameParameter("email")
// .failureHandler(authenticationFailureHandler())
// .permitAll());
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

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

View File

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

View File

@@ -0,0 +1,82 @@
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(@RequestParam String email, @RequestParam String password) {
// System.out.println(email);
// System.out.println(password);
//
// return "redirect:/";
// }
@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;
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.ui.Model;
import org.springframework.validation.BindingResult;
@@ -13,37 +13,45 @@ import ru.lionarius.isdojplab.repository.UserRepository;
@Controller
public class RegisterFormController {
@Autowired
private UserRepository userRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegisterFormController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@GetMapping("/register")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "register_form";
}
}
@PostMapping("/register")
public String submitForm(@Valid @ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "register_form";
}
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
bindingResult.rejectValue("email", "email.already.exists", "Электронная почта уже зарегистрирована");
return "register_form";
}
if (userRepository.findByName(user.getName()).isPresent()) {
bindingResult.rejectValue("name", "name.already.exists", "Имя уже зарегистрировано");
return "register_form";
}
var hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
userRepository.save(user);
model.addAttribute("message", "Пользователь успешно зарегистрирован");
return "register_form";
model.addAttribute("message", "Пользователь успешно зарегистрирован");
return "redirect:/login";
}
}

View File

@@ -1,6 +1,9 @@
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.Data;
import lombok.NoArgsConstructor;
@@ -13,9 +16,9 @@ public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
private String description;
private String imageUrl;
private String imageUrl;
}

View File

@@ -8,22 +8,38 @@ import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
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
@Entity
public class User {
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Поле не может быть пустым")
private String name;
private String name;
@NotBlank(message = "Поле не может быть пустым")
@Email(message = "Неверный формат электронной почты")
private String email;
@NotBlank(message = "Поле не может быть пустым")
@Size(min = 6, message = "Пароль должен состоять не менее 6 символов")
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;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import ru.lionarius.isdojplab.model.Product;
@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;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import ru.lionarius.isdojplab.model.User;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByName(String name);
}

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

@@ -1,5 +1,5 @@
<!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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -14,8 +14,16 @@
</header>
<div class="container">
<div class="register-container">
<a href="/register" class="register-button">Регистрация</a>
<div sec:authorize="!isAuthenticated()">
<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>
<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>
<p th:if="${message}" th:text="${message}"></p>
<a href="/login">Уже зарегистрированы?</a>
</div>
<footer>
<p>© 2024 Магазин электротоваров. Все права защищены.</p>
</footer>