There was an unexpected error (type=Forbidden, status=403)
Появилась проблема в проекте после добавления авторизации и пользователя (Spring Security
). С авторизацией все отлично работает, но когда пытаюсь добавить новый тур как пользователь вылетает ошибка 403, до добавления пользователя работало все отлично.
Вот страница html с добавлением:
<!DOCTYPE html>
<html>
<head>
<title>GEOGRAPHIYAPUTESHESTVIY</title>
</head>
<body>
<h1>GEOGRAPHIYAPUTESHESTVIY</h1><hr>
<#if user.email??>
<h3>Имя компании перевозчика: <i>${user.name}</i></h3>
<form action="/logout" method="post">
<input type="hidden" name="_csrf" value="${_csrf.token}">
<input type="submit" value="Выйти"/>
</form>
<#else>
<a href="/login">Войти</a></h1>
</#if>
<hr>
<form action="/logout" method="post">
<input type="hidden" name="_csrf" value="${_csrf.token}">
<input type="submit" value="Выйти"/>
</form>
<h4>Все возможные туры:</h4>
<form action="/" method="get">
Поиск по названию тура: <input type="text" name="title">
<input type="submit" value="Поиск"/>
</form>
<#list products as product>
<div>
<p><b>${product.title}</b> ${product.price} бел.руб. | <a href="/product/${product.id}">Подробнее...</a></p>
</div>
<#else>
<h3>Туров нет</h3>
</#list>
<#if user.email??>
<hr>
<h3>Добавить новый тур</h3>
<form action="/product/create" method="post" enctype="multipart/form-data">
Название тура: <input type="text" name="title"/><br><br>
Описание тура: <input type="text" name="description"/><br><br>
Стоимость: <input type="number" name="price"/><br><br>
Город: <input type="text" name="city"/><br><br>
Первая фотография: <input type="file" name="file1"/><br><br>
Вторая фотография: <input type="file" name="file2"/><br><br>
Третья фотография: <input type="file" name="file3"/><br><br>
<input type="hidden" name="_csrf" value="${_csrf.token}">
<input type="submit" value="Добавить тур"/>
</form>
</#if>
</body>
</html>
Конфиг SecurityConfig.java
:
import com.example.tourism.services.CustomUserDetailsService;
import lombok.RequiredArgsConstructor;
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.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import java.util.Collection;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers(("/"), "/registration").permitAll()
.requestMatchers("/product/**", "/image/**")
.hasAnyAuthority("ROLE_ADMIN","ROLE_USER")
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(8);
}
}
ProductService.java
:
import com.example.tourism.models.Image;
import com.example.tourism.models.Product;
import com.example.tourism.models.User;
import com.example.tourism.repositories.ProductRepository;
import com.example.tourism.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
@Service
@Slf4j
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
private final UserRepository userRepository;
public List<Product> listProducts(String title) {
if (title != null) return productRepository.findByTitle(title);
return productRepository.findAll();
}
public void saveProduct(Principal principal, Product product, MultipartFile file1, MultipartFile file2, MultipartFile file3) throws IOException {
product.setUser(getUserByPrincipal(principal));
Image image1;
Image image2;
Image image3;
if (file1.getSize() != 0) {
image1 = toImageEntity(file1);
image1.setPreviewImage(true);
product.addImageToProduct(image1);
}
if (file2.getSize() != 0) {
image2 = toImageEntity(file2);
product.addImageToProduct(image2);
}
if (file3.getSize() != 0) {
image3 = toImageEntity(file3);
product.addImageToProduct(image3);
}
log.info("Saving new Product. Title: {}; Author email: {}", product.getTitle(), product.getUser().getEmail());
Product productFromDb = productRepository.save(product);
productFromDb.setPreviewImageId(productFromDb.getImages().get(0).getId());
productRepository.save(product);
}
public User getUserByPrincipal(Principal principal) {
if (principal == null) return new User();
return userRepository.findByEmail(principal.getName());
}
private Image toImageEntity(MultipartFile file) throws IOException {
Image image = new Image();
image.setName(file.getName());
image.setOriginalFileName(file.getOriginalFilename());
image.setContentType(file.getContentType());
image.setSize(file.getSize());
image.setBytes(file.getBytes());
return image;
}
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
public Product getProductById(Long id) {
return productRepository.findById(id).orElse(null);
}
}
И контроллер ProductController.java
:
import com.example.tourism.models.Product;
import com.example.tourism.services.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.security.Principal;
@Controller
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/")
public String products(@RequestParam(name = "title", required = false) String title, Principal principal, Model model) {
model.addAttribute("products", productService.listProducts(title));
model.addAttribute("user", productService.getUserByPrincipal(principal));
return "products";
}
@GetMapping("/product/{id}")
public String productInfo(@PathVariable Long id, Model model) {
Product product = productService.getProductById(id);
model.addAttribute("product", product);
model.addAttribute("images", product.getImages());
return "product-info";
}
@PostMapping("/product/create")
public String createProduct(@RequestParam("file1") MultipartFile file1, @RequestParam("file2") MultipartFile file2,
@RequestParam("file3") MultipartFile file3, Product product, Principal principal) throws IOException {
productService.saveProduct(principal, product, file1, file2, file3);
return "redirect:/";
}
@PostMapping("/product/delete/{id}")
public String deleteProduct(@PathVariable Long id){
productService.deleteProduct(id);
return "redirect:/";
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
User.java
:
@Entity
@Table(name = "users")
@Data
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "email", unique = true)
private String email;
@Column(name = "phone_number")
private String phoneNumber;
@Column(name = "name")
private String name;
@Column(name = "active")
private boolean active;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "image_id")
private Image avatar;
@Column(name = "password", length = 1000)
private String password;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role",
joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles = new HashSet<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
private List<Product> products = new ArrayList<>();
private LocalDateTime dateOfCreated;
@PrePersist
private void init() {
dateOfCreated = LocalDateTime.now();
}
// security
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return active;
}
}
Enum Role.java
import org.springframework.security.core.GrantedAuthority;
public enum Role implements GrantedAuthority {
ROLE_USER, ROLE_ADMIN;
@Override
public String getAuthority() {
return null;
}
}