diff --git a/backend/ipLab/build.gradle b/backend/ipLab/build.gradle index 8e65326..7cea6c2 100644 --- a/backend/ipLab/build.gradle +++ b/backend/ipLab/build.gradle @@ -30,6 +30,12 @@ dependencies { implementation 'org.webjars:bootstrap:5.1.3' implementation 'org.webjars:jquery:3.6.0' implementation 'org.webjars:font-awesome:6.1.0' + + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + implementation 'com.auth0:java-jwt:4.4.0' + implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' + implementation 'javax.xml.bind:jaxb-api:2.3.1' } tasks.named('test') { diff --git a/backend/ipLab/data.mv.db b/backend/ipLab/data.mv.db index fcff4ed..cd412bd 100644 Binary files a/backend/ipLab/data.mv.db and b/backend/ipLab/data.mv.db differ diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/PasswordEncoderConfiguration.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..e4823ce --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package com.example.ipLab.StoreDataBase.Configurations; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public PasswordEncoder createPasswordEncoder(){ + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/SecurityConfiguration.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/SecurityConfiguration.java new file mode 100644 index 0000000..992e0d2 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/SecurityConfiguration.java @@ -0,0 +1,87 @@ +package com.example.ipLab.StoreDataBase.Configurations; + +import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtFilter; +import com.example.ipLab.StoreDataBase.Controllers.UserController; +import com.example.ipLab.StoreDataBase.MVC.UserLoginMVCController; +import com.example.ipLab.StoreDataBase.Model.UserRole; +import com.example.ipLab.StoreDataBase.Service.UserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +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.WebSecurityCustomizer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(securedEnabled = true) +public class SecurityConfiguration { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static final String LOGIN_URL = "/login"; + public static final String SPA_URL_MASK = "/{path:[^\\.]*}"; + private UserService userService; + private JwtFilter jwtFilter; + + public SecurityConfiguration(UserService userService) { + this.userService = userService; + this.jwtFilter = new JwtFilter(userService); + createAdminOnStartup(); + createTestUsersOnStartup(); + } + + private void createAdminOnStartup() { + final String admin = "admin"; + if (userService.getUserByLogin(admin) == null) { + log.info("Admin user successfully created"); + userService.addUser(admin, admin, admin, UserRole.ADMIN, 2L); + } + } + + private void createTestUsersOnStartup() { + final String[] userNames = {"user1", "user2", "user3"}; + final Long[] userId = {19052L, 19053L, 20652L}; + int cnt = 0; + for (String user : userNames) { + if (userService.getUserByLogin(user) == null) { + userService.addUser(user, user, user, UserRole.USER, userId[cnt]); + } + cnt++; + } + } + + @Bean + public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { + http.cors() + .and() + .csrf().disable() + .authorizeHttpRequests((a) -> a + .requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN") + .requestMatchers("/api/**").authenticated() + .requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll() + .requestMatchers(HttpMethod.GET, "/img/**").permitAll()) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .anonymous().and().authorizeHttpRequests((a) -> + a.requestMatchers(LOGIN_URL, UserLoginMVCController.SIGNUP_URL, "/h2-console/**") + .permitAll().requestMatchers("/users").hasRole("ADMIN").anyRequest().authenticated()) + .formLogin() + .loginPage(LOGIN_URL).permitAll() + .defaultSuccessUrl("/", true) + .and() + .logout().permitAll() + .logoutSuccessUrl("/login") + .and() + .userDetailsService(userService); + return http.build(); + } + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring().requestMatchers("/css/**", "/js/**", "/templates/**", "/webjars/**", "/styles/**"); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtException.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtException.java new file mode 100644 index 0000000..14261ef --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtException.java @@ -0,0 +1,11 @@ +package com.example.ipLab.StoreDataBase.Configurations.jwt; + +public class JwtException extends RuntimeException{ + public JwtException(Throwable throwable) { + super(throwable); + } + + public JwtException(String message) { + super(message); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtFilter.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtFilter.java new file mode 100644 index 0000000..2dc19d8 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtFilter.java @@ -0,0 +1,92 @@ +package com.example.ipLab.StoreDataBase.Configurations.jwt; + +import com.example.ipLab.StoreDataBase.Service.UserService; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.util.StringUtils; + +import java.io.IOException; + +public class JwtFilter extends AbstractPreAuthenticatedProcessingFilter { + private static final String AUTHORIZATION = "Authorization"; + public static final String TOKEN_BEGIN_STR = "Bearer "; + + private final UserService userService; + + public JwtFilter(UserService userService) { + this.userService = userService; + } + + private String getTokenFromRequest(HttpServletRequest request) { + String bearer = request.getHeader(AUTHORIZATION); + if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) { + return bearer.substring(TOKEN_BEGIN_STR.length()); + } + return null; + } + + private void raiseException(ServletResponse response, int status, String message) throws IOException { + if (response instanceof final HttpServletResponse httpResponse) { + httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); + httpResponse.setStatus(status); + final byte[] body = new ObjectMapper().writeValueAsBytes(message); + response.getOutputStream().write(body); + } + } + + @Override + public void doFilter(ServletRequest request, + ServletResponse response, + FilterChain chain) throws IOException, ServletException { + if (request instanceof final HttpServletRequest httpRequest) { + final String token = getTokenFromRequest(httpRequest); + if (StringUtils.hasText(token)) { + try { + final UserDetails user = userService.loadUserByToken(token); + final UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(auth); + } catch (JwtException e) { + raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); + return; + } catch (Exception e) { + e.printStackTrace(); + raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + String.format("Internal error: %s", e.getMessage())); + return; + } + } + } + HttpServletRequest httpRequest = (HttpServletRequest) request; + if (httpRequest.getRequestURI().startsWith("/api/")) { + // Для URL, начинающихся с /api/, выполняем проверку наличия токена + super.doFilter(request, response, chain); + } else { + // Для остальных URL выполняем авторизацию + chain.doFilter(request, response); + } + } + + @Override + protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { + String token = getTokenFromRequest(request); + // Возвращаем токен как принципала + return token; + } + + @Override + protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { + return new WebAuthenticationDetailsSource().buildDetails(request); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtProperties.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtProperties.java new file mode 100644 index 0000000..8553d2e --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtProperties.java @@ -0,0 +1,27 @@ +package com.example.ipLab.StoreDataBase.Configurations.jwt; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true) +public class JwtProperties { + private String devToken = ""; + private Boolean isDev = true; + + public String getDevToken() { + return devToken; + } + + public void setDevToken(String devToken) { + this.devToken = devToken; + } + + public Boolean isDev() { + return isDev; + } + + public void setDev(Boolean dev) { + isDev = dev; + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtsProvider.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtsProvider.java new file mode 100644 index 0000000..f6d76f9 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Configurations/jwt/JwtsProvider.java @@ -0,0 +1,47 @@ +package com.example.ipLab.StoreDataBase.Configurations.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +@Component +public class JwtsProvider { + @Value("jwt.secret") + private String secret; + + public String generateToken(String login, String role) { + Date date = Date.from(LocalDate.now().plusDays(15).atStartOfDay(ZoneId.systemDefault()).toInstant()); + + JwtBuilder builder = Jwts.builder() + .setSubject(login) + .setExpiration(date) + .signWith(SignatureAlgorithm.HS512, secret); + Claims claims = Jwts.claims(); + claims.put("role", role); + builder.addClaims(claims); + return builder.compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secret).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + public String getLogin(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Controllers/UserController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Controllers/UserController.java new file mode 100644 index 0000000..52e977a --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Controllers/UserController.java @@ -0,0 +1,24 @@ +package com.example.ipLab.StoreDataBase.Controllers; + +import com.example.ipLab.StoreDataBase.DTO.UserDTO; +import com.example.ipLab.StoreDataBase.Service.UserService; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class UserController { + public static final String URL_LOGIN = "/jwt/login"; + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @PostMapping(URL_LOGIN) + public String login(@RequestBody @Valid UserDTO userDto) { + return userService.loginAndGetToken(userDto); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/LoginDTO.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/LoginDTO.java new file mode 100644 index 0000000..4975dd6 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/LoginDTO.java @@ -0,0 +1,36 @@ +package com.example.ipLab.StoreDataBase.DTO; + +import jakarta.validation.constraints.NotBlank; + +public class LoginDTO { + @NotBlank + private String login; + @NotBlank + private String password; + @NotBlank + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordConfirm() { + return passwordConfirm; + } + + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/UserDTO.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/UserDTO.java new file mode 100644 index 0000000..fa471e7 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/DTO/UserDTO.java @@ -0,0 +1,49 @@ +package com.example.ipLab.StoreDataBase.DTO; + +import com.example.ipLab.StoreDataBase.Model.User; +import com.example.ipLab.StoreDataBase.Model.UserRole; + +public class UserDTO { + private long id; + private String login; + private UserRole role; + private String password; + + public UserDTO() { + } + + public UserDTO(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.role = user.getRole(); + this.password = user.getPassword(); + } + + public long getId() { + return id; + } + + public String getLogin() { + return login; + } + + public UserRole getRole() { + return role; + } + + public String getPassword() { + return password; + } + + public void setLogin(String login) { + this.login = login; + } + + public void setRole(UserRole role) { + this.role = role; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Exceptions/UserNotFoundException.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Exceptions/UserNotFoundException.java new file mode 100644 index 0000000..ede9957 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package com.example.ipLab.StoreDataBase.Exceptions; + +public class UserNotFoundException extends RuntimeException{ + public UserNotFoundException(String login){ + super(String.format("User with login: %s hasn't been found", login)); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/OrderedMVCController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/OrderedMVCController.java index 10018f9..02d498e 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/OrderedMVCController.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/OrderedMVCController.java @@ -3,10 +3,13 @@ package com.example.ipLab.StoreDataBase.MVC; import com.example.ipLab.StoreDataBase.DTO.CustomerDTO; import com.example.ipLab.StoreDataBase.DTO.OrderedDTO; import com.example.ipLab.StoreDataBase.DTO.ProductDTO; +import com.example.ipLab.StoreDataBase.Model.CustomUser; +import com.example.ipLab.StoreDataBase.Model.UserRole; import com.example.ipLab.StoreDataBase.Service.CustomerService; import com.example.ipLab.StoreDataBase.Service.OrderService; import com.example.ipLab.StoreDataBase.Service.ProductService; import jakarta.validation.Valid; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -26,11 +29,19 @@ public class OrderedMVCController { } @GetMapping - public String getOrdereds(Model model) { - model.addAttribute("orders", - orderedService.getAllOrders().stream() - .map(OrderedDTO::new) - .toList()); + public String getOrdereds(Model model, @AuthenticationPrincipal CustomUser user) { + if (user.getRole() == UserRole.USER){ + model.addAttribute("orders", + orderedService.getOrdersByCustomerId(user.getUserID()).stream() + .map(OrderedDTO::new) + .toList()); + } + else { + model.addAttribute("orders", + orderedService.getAllOrders().stream() + .map(OrderedDTO::new) + .toList()); + } return "order"; } diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/ProductMVCController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/ProductMVCController.java index 305ff2f..880cd25 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/ProductMVCController.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/ProductMVCController.java @@ -1,13 +1,24 @@ package com.example.ipLab.StoreDataBase.MVC; +import com.example.ipLab.StoreDataBase.Configurations.SecurityConfiguration; import com.example.ipLab.StoreDataBase.DTO.ProductDTO; +import com.example.ipLab.StoreDataBase.Model.CustomUser; +import com.example.ipLab.StoreDataBase.Model.User; +import com.example.ipLab.StoreDataBase.Model.UserRole; import com.example.ipLab.StoreDataBase.Service.ProductService; +import com.example.ipLab.StoreDataBase.Service.UserService; import jakarta.validation.Valid; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; +import java.security.Principal; + @Controller @RequestMapping("/product") public class ProductMVCController { @@ -18,11 +29,21 @@ public class ProductMVCController { } @GetMapping - public String getProducts(Model model) { - model.addAttribute("products", - productService.getAllProducts().stream() - .map(ProductDTO::new) - .toList()); + + public String getProducts(Model model, @AuthenticationPrincipal CustomUser user) { + if (user.getRole() == UserRole.USER){ + model.addAttribute("products", + productService.getAllProductsWithStores().stream() + .map(ProductDTO::new) + .toList()); + } + else{ + model.addAttribute("products", + productService.getAllProducts().stream() + .map(ProductDTO::new) + .toList()); + } + return "product"; } diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserLoginMVCController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserLoginMVCController.java new file mode 100644 index 0000000..2a5d192 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserLoginMVCController.java @@ -0,0 +1,51 @@ +package com.example.ipLab.StoreDataBase.MVC; + +import com.example.ipLab.StoreDataBase.DTO.LoginDTO; +import com.example.ipLab.StoreDataBase.Model.User; +import com.example.ipLab.StoreDataBase.Model.UserRole; +import com.example.ipLab.StoreDataBase.Service.UserService; +import com.example.ipLab.StoreDataBase.util.validation.ValidationException; +import jakarta.validation.Valid; +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 org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping(UserLoginMVCController.SIGNUP_URL) +public class UserLoginMVCController { + public static final String SIGNUP_URL = "/signup"; + + private final UserService userService; + + public UserLoginMVCController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public String showSignupForm(Model model) { + model.addAttribute("userDto", new LoginDTO()); + return "signup"; + } + + @PostMapping + public String signup(@ModelAttribute("userDto") @Valid LoginDTO userSignupDto, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + model.addAttribute("errors", bindingResult.getAllErrors()); + return "signup"; + } + try { + final User user = userService.addUser( + userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm(), UserRole.USER, Long.parseLong("1")); + return "redirect:/login?created=" + user.getLogin(); + } catch (ValidationException e) { + model.addAttribute("errors", e.getMessage()); + return "signup"; + } + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserMVCController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserMVCController.java new file mode 100644 index 0000000..3659995 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/MVC/UserMVCController.java @@ -0,0 +1,33 @@ +package com.example.ipLab.StoreDataBase.MVC; + +import com.example.ipLab.StoreDataBase.DTO.ProductDTO; +import com.example.ipLab.StoreDataBase.DTO.UserDTO; +import com.example.ipLab.StoreDataBase.Model.UserRole; +import com.example.ipLab.StoreDataBase.Service.UserService; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/user") +public class UserMVCController { + private final UserService userService; + + public UserMVCController(UserService userService) { + this.userService = userService; + } + + @GetMapping + @Secured({UserRole.AsString.ADMIN}) + public String getUsers(Model model){ + model.addAttribute("users", + userService.getAllUsers().stream() + .map(UserDTO::new) + .toList()); + return "users"; + } + + +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/CustomUser.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/CustomUser.java new file mode 100644 index 0000000..05ed8f0 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/CustomUser.java @@ -0,0 +1,26 @@ +package com.example.ipLab.StoreDataBase.Model; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.util.Collection; + +public class CustomUser extends User { + private final Long userID; + private final UserRole role; + + public CustomUser(String username, String password, + Collection authorities, Long userID, UserRole role) { + super(username, password, authorities); + this.userID = userID; + this.role = role; + } + + public Long getUserID() { + return userID; + } + + public UserRole getRole() { + return role; + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/User.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/User.java new file mode 100644 index 0000000..aeead35 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/User.java @@ -0,0 +1,76 @@ +package com.example.ipLab.StoreDataBase.Model; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; + +import java.util.Objects; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @Column + @NotBlank + private String login; + @Column + @NotBlank + private String password; + @Column + @NotBlank + private Long userId; + + private UserRole role; + + public User() { + } + + public User(String login, String password, Long userId) { + this(login, password, UserRole.USER, userId); + } + + public User(String login, String password, UserRole role, Long userId) { + this.login = login; + this.password = password; + this.role = role; + this.userId = userId; + } + + public Long getId() { + return id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public UserRole getRole() { + return role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(login, user.login); + } + + @Override + public int hashCode() { + return Objects.hash(id, login); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/UserRole.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/UserRole.java new file mode 100644 index 0000000..aa77b24 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Model/UserRole.java @@ -0,0 +1,20 @@ +package com.example.ipLab.StoreDataBase.Model; + +import org.springframework.security.core.GrantedAuthority; + +public enum UserRole implements GrantedAuthority { + ADMIN, + USER; + + private static final String PREFIX = "ROLE_"; + + @Override + public String getAuthority() { + return PREFIX + this.name(); + } + + public static final class AsString { + public static final String ADMIN = PREFIX + "ADMIN"; + public static final String USER = PREFIX + "USER"; + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/OrderedRepository.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/OrderedRepository.java index 45a6129..ce6489e 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/OrderedRepository.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/OrderedRepository.java @@ -1,7 +1,13 @@ package com.example.ipLab.StoreDataBase.Repositories; import com.example.ipLab.StoreDataBase.Model.Ordered; +import com.example.ipLab.StoreDataBase.Model.Product; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Collection; public interface OrderedRepository extends JpaRepository { + @Query("SELECT o FROM Ordered o WHERE o.customer.id = ?1") + Collection findOrdersByCustomerId(Long clientId); } diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/UserRepository.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/UserRepository.java new file mode 100644 index 0000000..1f248f2 --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Repositories/UserRepository.java @@ -0,0 +1,8 @@ +package com.example.ipLab.StoreDataBase.Repositories; + +import com.example.ipLab.StoreDataBase.Model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + User findOneByLogin(String login); +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/OrderService.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/OrderService.java index 2ce83e4..bc4cce0 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/OrderService.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/OrderService.java @@ -52,6 +52,11 @@ public class OrderService { return orderedRepository.findAll(); } + @Transactional + public List getOrdersByCustomerId(Long customerId){ + return orderedRepository.findOrdersByCustomerId(customerId).stream().toList(); + } + @Transactional public Ordered updateOrder(Long id, int quantity){ final Ordered order = getOrder(id); diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/ProductService.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/ProductService.java index 6668ce1..83593bd 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/ProductService.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/ProductService.java @@ -36,7 +36,6 @@ public class ProductService { public Product getProduct(Long id){ return productRepository.findById(id).orElseThrow(() -> new ProductNotFoundException(id)); } - @Transactional public List getAllProducts(){ return productRepository.findAll(); diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/UserService.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/UserService.java new file mode 100644 index 0000000..951735e --- /dev/null +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/Service/UserService.java @@ -0,0 +1,97 @@ +package com.example.ipLab.StoreDataBase.Service; + +import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtException; +import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtsProvider; +import com.example.ipLab.StoreDataBase.DTO.UserDTO; +import com.example.ipLab.StoreDataBase.Exceptions.CustomerNotFoundException; +import com.example.ipLab.StoreDataBase.Exceptions.UserNotFoundException; +import com.example.ipLab.StoreDataBase.Model.CustomUser; +import com.example.ipLab.StoreDataBase.Model.Customer; +import com.example.ipLab.StoreDataBase.Model.User; +import com.example.ipLab.StoreDataBase.Model.UserRole; +import com.example.ipLab.StoreDataBase.Repositories.UserRepository; +import com.example.ipLab.StoreDataBase.util.validation.ValidationException; +import com.example.ipLab.StoreDataBase.util.validation.ValidatorUtil; +import jakarta.transaction.Transactional; +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 java.util.Collections; +import java.util.List; +import java.util.Objects; + +@Service +public class UserService implements UserDetailsService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final ValidatorUtil validatorUtil; + private final JwtsProvider jwtProvider; + + public UserService(UserRepository userRepository, + PasswordEncoder passwordEncoder, + ValidatorUtil validatorUtil, + JwtsProvider jwtProvider) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.validatorUtil = validatorUtil; + this.jwtProvider = jwtProvider; + } + + @Transactional + public User addUser(String login, String password, String passwordConfirm, UserRole role, Long userId){ + if (getUserByLogin(login) != null){ + throw new ValidationException(String.format("User with login %s already exists", login)); + } + if (!Objects.equals(password, passwordConfirm)) { + throw new ValidationException("Passwords not equals"); + } + final User user = new User(login, passwordEncoder.encode(password), role, userId); + validatorUtil.validate(user); + return userRepository.save(user); + } + + @Transactional + public User getUserByLogin(String login){ + return userRepository.findOneByLogin(login); + } + + @Transactional + public List getAllUsers(){ + return userRepository.findAll(); + } + + public UserDetails loadUserByToken(String token) throws UsernameNotFoundException { + if (!jwtProvider.validateToken(token)) { + throw new JwtException("Bad token"); + } + final String userLogin = jwtProvider.getLogin(token); + if (userLogin.isEmpty()) { + throw new JwtException("Token is not contain Login"); + } + return loadUserByUsername(userLogin); + } + + public String loginAndGetToken(UserDTO userDto) { + final User user = getUserByLogin(userDto.getLogin()); + if (user == null) { + throw new UserNotFoundException(userDto.getLogin()); + } + if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) { + throw new UserNotFoundException(user.getLogin()); + } + return jwtProvider.generateToken(user.getLogin(), user.getRole().name()); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User userEntity = getUserByLogin(username); + if (userEntity == null) { + throw new UsernameNotFoundException(username); + } + return new CustomUser( + userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()), userEntity.getId(), userEntity.getRole()); + } +} diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/error/AdviceController.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/error/AdviceController.java index 2a261cf..3e37134 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/error/AdviceController.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/error/AdviceController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; +import java.nio.file.AccessDeniedException; import java.util.stream.Collectors; @ControllerAdvice(annotations = RestController.class) @@ -35,6 +36,14 @@ public class AdviceController { return handleException(validationException); } + @ExceptionHandler({ + AccessDeniedException.class + }) + public ResponseEntity handleAccessDeniedException(Throwable e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(Exception.class) public ResponseEntity handleUnknownException(Throwable e) { e.printStackTrace(); diff --git a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/validation/ValidationException.java b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/validation/ValidationException.java index 41bbcac..6fcfb85 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/validation/ValidationException.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/StoreDataBase/util/validation/ValidationException.java @@ -6,4 +6,8 @@ public class ValidationException extends RuntimeException{ public ValidationException(Set errors){ super(String.join("\n", errors)); } + + public ValidationException(String error){ + super(error); + } } diff --git a/backend/ipLab/src/main/java/com/example/ipLab/WebConfiguration.java b/backend/ipLab/src/main/java/com/example/ipLab/WebConfiguration.java index 8db3443..24b259e 100644 --- a/backend/ipLab/src/main/java/com/example/ipLab/WebConfiguration.java +++ b/backend/ipLab/src/main/java/com/example/ipLab/WebConfiguration.java @@ -2,11 +2,18 @@ package com.example.ipLab; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfiguration implements WebMvcConfigurer { public static final String REST_API = "/api"; + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + WebMvcConfigurer.super.addViewControllers(registry); + registry.addViewController("login"); + } @Override public void addCorsMappings(CorsRegistry registry){ registry.addMapping("/**").allowedMethods("*"); diff --git a/backend/ipLab/src/main/resources/application.properties b/backend/ipLab/src/main/resources/application.properties index da7b0b1..d4ce457 100644 --- a/backend/ipLab/src/main/resources/application.properties +++ b/backend/ipLab/src/main/resources/application.properties @@ -9,3 +9,6 @@ spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true spring.h2.console.settings.trace=false spring.h2.console.settings.web-allow-others=false +jwt.dev-token=my-secret-jwt +jwt.dev=true +jwt.secret = my-secret-jwt diff --git a/backend/ipLab/src/main/resources/static/img/logo.png b/backend/ipLab/src/main/resources/static/img/logo.png new file mode 100644 index 0000000..f7c2e6e Binary files /dev/null and b/backend/ipLab/src/main/resources/static/img/logo.png differ diff --git a/backend/ipLab/src/main/resources/templates/addToStore.html b/backend/ipLab/src/main/resources/templates/addToStore.html index 720e9c1..19d180a 100644 --- a/backend/ipLab/src/main/resources/templates/addToStore.html +++ b/backend/ipLab/src/main/resources/templates/addToStore.html @@ -5,7 +5,7 @@ -
+
diff --git a/backend/ipLab/src/main/resources/templates/customer.html b/backend/ipLab/src/main/resources/templates/customer.html index 16afff1..352bbed 100644 --- a/backend/ipLab/src/main/resources/templates/customer.html +++ b/backend/ipLab/src/main/resources/templates/customer.html @@ -5,7 +5,7 @@ -
+
@@ -17,6 +17,7 @@ # + ID Фамилия Имя Отчество @@ -26,6 +27,7 @@ + diff --git a/backend/ipLab/src/main/resources/templates/default.html b/backend/ipLab/src/main/resources/templates/default.html index e56935f..4f1eb5f 100644 --- a/backend/ipLab/src/main/resources/templates/default.html +++ b/backend/ipLab/src/main/resources/templates/default.html @@ -23,7 +23,7 @@
- * + *
boxStore @@ -31,7 +31,7 @@
diff --git a/backend/ipLab/src/main/resources/templates/index.html b/backend/ipLab/src/main/resources/templates/index.html index 751437c..1e4a266 100644 --- a/backend/ipLab/src/main/resources/templates/index.html +++ b/backend/ipLab/src/main/resources/templates/index.html @@ -6,7 +6,7 @@
-

Добро пожаловать!

+

Добро пожаловать, !

\ No newline at end of file diff --git a/backend/ipLab/src/main/resources/templates/login.html b/backend/ipLab/src/main/resources/templates/login.html new file mode 100644 index 0000000..7b24fe5 --- /dev/null +++ b/backend/ipLab/src/main/resources/templates/login.html @@ -0,0 +1,43 @@ + + + + + +
+
+ Пользователь не найден или пароль указан не верно +
+
+ Выход успешно произведен +
+
+ Пользователь '' успешно создан +
+
+
+
+
+
Авторизация
+ +
+ + +
+
+ + +
+ + +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/backend/ipLab/src/main/resources/templates/order.html b/backend/ipLab/src/main/resources/templates/order.html index 0c14220..7e84c0e 100644 --- a/backend/ipLab/src/main/resources/templates/order.html +++ b/backend/ipLab/src/main/resources/templates/order.html @@ -6,7 +6,7 @@
-
+
Добавить diff --git a/backend/ipLab/src/main/resources/templates/product.html b/backend/ipLab/src/main/resources/templates/product.html index 3211ccb..2b53163 100644 --- a/backend/ipLab/src/main/resources/templates/product.html +++ b/backend/ipLab/src/main/resources/templates/product.html @@ -6,7 +6,7 @@
-
+
Добавить @@ -19,7 +19,7 @@ # Название товара Название магазина - + @@ -27,7 +27,7 @@ - +