- Добавил шифрование паролей в БД.
- Добавил валидацию полей ввода. https://www.youtube.com/watch?v=AdLXmE4rjy4&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=14
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -77,6 +77,10 @@
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -6,6 +6,8 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@@ -13,6 +15,10 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||
@EnableMethodSecurity
|
||||
public class WebSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(8);
|
||||
}
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.sytes.kashey.sweater.controller;
|
||||
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ControllerUtils {
|
||||
static Map<String, String> getErrors(BindingResult bindingResult) {
|
||||
|
||||
Collector<FieldError, ?, Map<String, String>> collector = Collectors.toMap(
|
||||
fieldError -> fieldError.getField() + "Error",
|
||||
FieldError::getDefaultMessage
|
||||
);
|
||||
return bindingResult.getFieldErrors().stream().collect(collector);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.sytes.kashey.sweater.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import net.sytes.kashey.sweater.domain.Message;
|
||||
import net.sytes.kashey.sweater.repository.MessageRepository;
|
||||
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
||||
@@ -7,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -14,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -48,25 +51,34 @@ public class MainController {
|
||||
|
||||
@PostMapping("/main")
|
||||
public String addMessage(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||
@RequestParam String text,
|
||||
@RequestParam String tag,
|
||||
@RequestParam("file") MultipartFile file,
|
||||
Model model) throws IOException {
|
||||
@Valid Message message,
|
||||
BindingResult bindingResult,
|
||||
Model model,
|
||||
@RequestParam("file") MultipartFile file
|
||||
) throws IOException {
|
||||
|
||||
Message newMessage = new Message(text, tag, customUserDetails.getUser());
|
||||
message.setAuthor(customUserDetails.getUser());
|
||||
|
||||
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
||||
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
if (bindingResult.hasErrors()) {
|
||||
Map<String, String> errorsMap = ControllerUtils.getErrors(bindingResult);
|
||||
model.mergeAttributes(errorsMap);
|
||||
model.addAttribute("message", message);
|
||||
} else {
|
||||
|
||||
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
||||
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
}
|
||||
String uuidFile = UUID.randomUUID().toString();
|
||||
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
||||
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
||||
message.setFilename(resultFilename);
|
||||
}
|
||||
String uuidFile = UUID.randomUUID().toString();
|
||||
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
||||
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
||||
newMessage.setFilename(resultFilename);
|
||||
}
|
||||
|
||||
messageRepository.save(newMessage);
|
||||
messageRepository.save(message);
|
||||
model.addAttribute("message", null);
|
||||
}
|
||||
model.addAttribute("messages", messageRepository.findAll());
|
||||
model.addAttribute("tag", "");
|
||||
return "main";
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package net.sytes.kashey.sweater.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import net.sytes.kashey.sweater.domain.User;
|
||||
import net.sytes.kashey.sweater.repository.UserRepository;
|
||||
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
||||
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.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
public class RegistrationController {
|
||||
private final CustomUserDetailsService customUserDetailsService;
|
||||
@@ -27,11 +26,20 @@ public class RegistrationController {
|
||||
}
|
||||
|
||||
@PostMapping("/registration")
|
||||
public String addUser(@ModelAttribute User user, Map<String, Object> model) {
|
||||
public String addUser(@ModelAttribute @Valid User user, BindingResult bindingResult, Model model) {
|
||||
|
||||
if (user.getPassword() != null && !user.getPassword().equals(user.getPassword2())) {
|
||||
model.addAttribute("passwordError", "Passwords do not match!");
|
||||
}
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.mergeAttributes(ControllerUtils.getErrors(bindingResult));
|
||||
return "registration";
|
||||
}
|
||||
|
||||
String result = customUserDetailsService.addUser(user);
|
||||
if (!result.isEmpty()) {
|
||||
model.put("message", result);
|
||||
model.addAttribute("usernameError", result);
|
||||
return "registration";
|
||||
} else {
|
||||
return "redirect:/login";
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package net.sytes.kashey.sweater.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
@Entity
|
||||
public class Message {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "Please fill in the field.")
|
||||
@Length(max = 2048, message = "Message too long.")
|
||||
private String text;
|
||||
@Length(max = 255, message = "Tag too long.")
|
||||
private String tag;
|
||||
|
||||
private String filename;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.sytes.kashey.sweater.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -11,10 +13,18 @@ public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@NotBlank(message = "Username must not be empty.")
|
||||
private String username;
|
||||
@NotBlank(message = "Password must not be empty.")
|
||||
private String password;
|
||||
|
||||
@Transient
|
||||
@NotBlank(message = "Password confirmation must not be empty.")
|
||||
private String password2;
|
||||
private boolean enabled;
|
||||
|
||||
@Email(message = "E-mail is not valid.")
|
||||
@NotBlank(message = "E-mail must not be empty.")
|
||||
private String email;
|
||||
|
||||
private String activationCode;
|
||||
@@ -90,4 +100,12 @@ public class User {
|
||||
public void setActivationCode(String activationCode) {
|
||||
this.activationCode = activationCode;
|
||||
}
|
||||
|
||||
public String getPassword2() {
|
||||
return password2;
|
||||
}
|
||||
|
||||
public void setPassword2(String password2) {
|
||||
this.password2 = password2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
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.util.ObjectUtils;
|
||||
|
||||
@@ -19,19 +20,26 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final EmailService emailService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Value("${hosting.path}")
|
||||
private String hostingPath;
|
||||
|
||||
public CustomUserDetailsService(UserRepository userRepository, EmailService emailService) {
|
||||
public CustomUserDetailsService(UserRepository userRepository, EmailService emailService,
|
||||
PasswordEncoder passwordEncoder) {
|
||||
|
||||
this.userRepository = userRepository;
|
||||
this.emailService = emailService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
|
||||
|
||||
if (name == null) {
|
||||
throw new UsernameNotFoundException("Username is empty!");
|
||||
}
|
||||
|
||||
User user = userRepository.findByUsername(name)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
|
||||
return new CustomUserDetails(user);
|
||||
@@ -50,7 +58,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
user.setEnabled(true);
|
||||
user.setActivationCode(UUID.randomUUID().toString());
|
||||
user.setRoles(Collections.singleton(Role.USER));
|
||||
user.setPassword("{noop}" + user.getPassword()); //пока не введено шифрование паролей
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
userRepository.save(user);
|
||||
|
||||
sendMessage(user);
|
||||
@@ -114,7 +122,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(password)) {
|
||||
user.setPassword("{noop}" + password);
|
||||
user.setPassword(password);
|
||||
}
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
insert into usr (id, username, password, enabled)
|
||||
values (1, 'admin', '{noop}admin', true);
|
||||
values (1, 'admin', '$2a$12$.XB1cxFOs6F8vgouixcTP.TaY2GXwFspYHID12JpLwotvoYI0JwnC', true);
|
||||
|
||||
insert into user_role (user_id, roles)
|
||||
values (1, 'USER'), (1, 'ADMIN');
|
||||
@@ -2,8 +2,10 @@
|
||||
<#import "parts/login.ftl" as l>
|
||||
|
||||
<@com.page>
|
||||
<#if message??>
|
||||
<div class="mb-3"> ${message} </div>
|
||||
<#if Session?? && Session.SPRING_SECURITY_LAST_EXCEPTION??>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
${Session.SPRING_SECURITY_LAST_EXCEPTION.message}
|
||||
</div>
|
||||
</#if>
|
||||
<@l.login "/login" false/>
|
||||
</@com.page>
|
||||
@@ -12,14 +12,26 @@
|
||||
aria-controls="collapseExample">
|
||||
Click to add message
|
||||
</a>
|
||||
<div class="collapse" id="collapseExample">
|
||||
<div class="collapse <#if message??>show</#if>" id="collapseExample">
|
||||
<div class="form-group mt-3">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="text" placeholder="Text"/>
|
||||
<input type="text" class="form-control ${(textError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.text}</#if>" name="text" placeholder="Text" />
|
||||
<#if textError??>
|
||||
<div class="invalid-feedback">
|
||||
${textError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="tag" placeholder="Tag">
|
||||
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag">
|
||||
<#if tagError??>
|
||||
<div class="invalid-feedback">
|
||||
${tagError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-file">
|
||||
|
||||
@@ -3,24 +3,57 @@
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label"> User Name:</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="username" placeholder="User name"/>
|
||||
<input type="text" name="username" value="<#if user??>${user.username}</#if>"
|
||||
class="form-control ${(usernameError??)?string('is-invalid', '')}"
|
||||
placeholder="User name"/>
|
||||
<#if usernameError??>
|
||||
<div class="invalid-feedback">
|
||||
${usernameError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="inputPassword3" class="col-sm-2 col-form-label"> Password: </label>
|
||||
<div class="col-sm-6">
|
||||
<input type="password" class="form-control" name="password" placeholder="Password">
|
||||
<input type="password" name="password"
|
||||
class="form-control ${(passwordError??)?string('is-invalid', '')}"
|
||||
placeholder="Password"/>
|
||||
<#if passwordError??>
|
||||
<div class="invalid-feedback">
|
||||
${passwordError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<#if isRegistrationForm>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">Password:</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="password" name="password2"
|
||||
class="form-control ${(password2Error??)?string('is-invalid', '')}"
|
||||
placeholder="Confirm password"/>
|
||||
<#if password2Error??>
|
||||
<div class="invalid-feedback">
|
||||
${password2Error}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label"> E-mail:</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="email" class="form-control" name="email" placeholder="E-mail"/>
|
||||
<input type="email" name="email" value="<#if user??>${user.email}</#if>"
|
||||
class="form-control ${(emailError??)?string('is-invalid', '')}"
|
||||
placeholder="E-m@il"/>
|
||||
<#if emailError??>
|
||||
<div class="invalid-feedback">
|
||||
${emailError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if !isRegistrationForm>
|
||||
<a href="/registration" class="btn btn-primary">Add new user</a>
|
||||
</#if>
|
||||
|
||||
Reference in New Issue
Block a user