+
+
+
\ No newline at end of file
diff --git a/frontend/spa-vue/src/pages/login.vue b/frontend/spa-vue/src/pages/login.vue
new file mode 100644
index 0000000..c8c850f
--- /dev/null
+++ b/frontend/spa-vue/src/pages/login.vue
@@ -0,0 +1,83 @@
+
+
+
+
\ No newline at end of file
diff --git a/frontend/spa-vue/src/pages/users.vue b/frontend/spa-vue/src/pages/users.vue
new file mode 100644
index 0000000..3543375
--- /dev/null
+++ b/frontend/spa-vue/src/pages/users.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
ID
+
Логин
+
Роль
+
+
+
+
+
{{ user.id }}
+
{{ user.login }}
+
{{ user.role }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/spa-vue/src/routes.js b/frontend/spa-vue/src/routes.js
index bf9f72a..0211afd 100644
--- a/frontend/spa-vue/src/routes.js
+++ b/frontend/spa-vue/src/routes.js
@@ -1,12 +1,20 @@
import manufacturers from './pages/manufacturers.vue'
import categories from './pages/categories.vue'
import products from './pages/products.vue'
+import users from "./pages/users.vue";
+import login from "./pages/login.vue";
+import registration from "./pages/registration.vue";
+import Error from "./pages/Error.vue";
import {createRouter, createWebHistory} from "vue-router"
const routes = [
{path: '/manufacturer', component: manufacturers},
{path: '/category', component: categories},
{path: '/product', component: products},
+ {path: "/users", component: users, meta: { requiresAuth: true, requiresAdmin: true }},
+ {path: "/login", component: login},
+ {path: "/registration", component: registration},
+ {path: "/error", component: Error, meta: { requiresAuth: true }},
]
const router = createRouter({
@@ -15,4 +23,22 @@ const router = createRouter({
routes
})
+router.beforeEach((to, from, next) => {
+ const isAuthenticated = localStorage.getItem("token");
+ if (to.matched.some((route) => route.meta.requiresAuth)) {
+ if (!isAuthenticated) {
+ next("/login");
+ return;
+ }
+ }
+ const isAdmin = localStorage.getItem("role") === "ADMIN";
+ if (to.matched.some((route) => route.meta.requiresAdmin)) {
+ if (!isAdmin) {
+ next("/error");
+ return;
+ }
+ }
+ next();
+});
+
export default router;
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserController.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserController.java
new file mode 100644
index 0000000..03db6ba
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserController.java
@@ -0,0 +1,64 @@
+package ru.ulstu.is.sbapp.HardwareShop.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.ValidationException;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.web.bind.annotation.*;
+import ru.ulstu.is.sbapp.HardwareShop.models.User;
+import ru.ulstu.is.sbapp.HardwareShop.models.UserRole;
+import ru.ulstu.is.sbapp.HardwareShop.services.UserService;
+
+import java.util.List;
+
+@RestController
+public class UserController {
+ public static final String URL_LOGIN = "/jwt/login";
+ public static final String URL_SIGN_UP = "/sign_up";
+ public static final String URL_WHO_AM_I = "/who_am_i";
+ 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);
+ }
+ @GetMapping(URL_WHO_AM_I)
+ public String role(@RequestParam("userLogin") String userLogin) {
+ return userService.findByLogin(userLogin).getRole().name();
+ }
+ @PostMapping(URL_SIGN_UP)
+ public String signUp(@RequestBody @Valid UserSignUpDTO userSignupDto) {
+ try {
+ final User user = userService.addUser(userSignupDto.getLogin(),
+ userSignupDto.getPassword(), userSignupDto.getPasswordConfirm(), UserRole.USER);
+ return "created " + user.getLogin();
+ } catch (ValidationException e) {
+ return e.getMessage();
+ }
+ }
+ @GetMapping("/{id}")
+ @Secured({UserRole.AsString.ADMIN})
+ public UserDto getUser(@PathVariable Long id) {
+ return new UserDto(userService.findUser(id));
+ }
+ @GetMapping("/")
+ @Secured({UserRole.AsString.ADMIN})
+ public List getUsers() {
+ return userService.findAllUsers().stream()
+ .map(UserDto::new)
+ .toList();
+ }
+
+ @PutMapping("/{id}")
+ public UserDto updateUser(@RequestBody @Valid UserDto userDto){
+ return new UserDto(userService.updateUser(userDto));
+ }
+
+
+ @DeleteMapping("/{id}")
+ public void deleteUser(@PathVariable Long id) {
+ userService.deleteUser(id);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java
new file mode 100644
index 0000000..049af37
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserDto.java
@@ -0,0 +1,50 @@
+package ru.ulstu.is.sbapp.HardwareShop.controller;
+
+import ru.ulstu.is.sbapp.HardwareShop.models.User;
+import ru.ulstu.is.sbapp.HardwareShop.models.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 setId(Long id) {
+ this.id = id;
+ }
+
+ public void setLogin(String login)
+ {
+ this.login = login;
+ }
+
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserSignUpDTO.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserSignUpDTO.java
new file mode 100644
index 0000000..8235bf3
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/controller/UserSignUpDTO.java
@@ -0,0 +1,40 @@
+package ru.ulstu.is.sbapp.HardwareShop.controller;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+public class UserSignUpDTO {
+ @NotBlank
+ @Size(min = 3, max = 64)
+ private String login;
+ @NotBlank
+ @Size(min = 6, max = 64)
+ private String password;
+ @NotBlank
+ @Size(min = 6, max = 64)
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java
new file mode 100644
index 0000000..980e85e
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/User.java
@@ -0,0 +1,91 @@
+package ru.ulstu.is.sbapp.HardwareShop.models;
+
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import ru.ulstu.is.sbapp.HardwareShop.controller.UserSignUpDTO;
+
+import javax.persistence.*;
+import java.util.Objects;
+
+@Entity
+@Table(name = "users")
+public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ @Column(nullable = false, unique = true, length = 64)
+ @NotBlank
+ @Size(min = 3, max = 64)
+ private String login;
+ @Column(nullable = false, length = 64)
+ @NotBlank
+ @Size(min = 6, max = 64)
+ private String password;
+ private UserRole role;
+
+ public User() {
+ }
+
+ public User(String login, String password) {
+ this(login, password, UserRole.USER);
+ }
+
+ public User(String login, String password, UserRole role) {
+ this.login = login;
+ this.password = password;
+ this.role = role;
+ }
+
+ public User(UserSignUpDTO userDto){
+ this.login = userDto.getLogin();
+ this.password = userDto.getPassword();
+ this.role = UserRole.USER;
+ }
+
+ 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);
+ }
+ @Override
+ public String toString() {
+ return "User{" +
+ "id=" + id +
+ ", login='" + login + '\'' +
+ ", password='" + password + '\'' +
+ ", role='" + role + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java
new file mode 100644
index 0000000..174da83
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/models/UserRole.java
@@ -0,0 +1,20 @@
+package ru.ulstu.is.sbapp.HardwareShop.models;
+
+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";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java
new file mode 100644
index 0000000..02f2524
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/repository/UserRepository.java
@@ -0,0 +1,8 @@
+package ru.ulstu.is.sbapp.HardwareShop.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.ulstu.is.sbapp.HardwareShop.models.User;
+
+public interface UserRepository extends JpaRepository {
+ User findOneByLoginIgnoreCase(String login);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java
new file mode 100644
index 0000000..a6b39ab
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserNotFoundException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.is.sbapp.HardwareShop.services;
+
+public class UserNotFoundException extends RuntimeException{
+ public UserNotFoundException(Long id) {
+ super(String.format("User with id [%s] is not found", id));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java
new file mode 100644
index 0000000..6d497de
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/HardwareShop/services/UserService.java
@@ -0,0 +1,129 @@
+package ru.ulstu.is.sbapp.HardwareShop.services;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+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.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import ru.ulstu.is.sbapp.HardwareShop.controller.UserDto;
+import ru.ulstu.is.sbapp.HardwareShop.controller.UserSignUpDTO;
+import ru.ulstu.is.sbapp.HardwareShop.models.User;
+import ru.ulstu.is.sbapp.HardwareShop.models.UserRole;
+import ru.ulstu.is.sbapp.HardwareShop.repository.UserRepository;
+
+import jakarta.validation.ValidationException;
+import ru.ulstu.is.sbapp.jwt.JwtException;
+import ru.ulstu.is.sbapp.jwt.JwtProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+@Service
+public class UserService implements UserDetailsService {
+ private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
+ private final JwtProvider jwtProvider;
+
+ public UserService(UserRepository userRepository,
+ PasswordEncoder passwordEncoder,
+ JwtProvider jwtProvider){
+ this.userRepository = userRepository;
+ this.passwordEncoder = passwordEncoder;
+ this.jwtProvider = jwtProvider;
+ }
+ public User findByLogin(String login) {
+ return userRepository.findOneByLoginIgnoreCase(login);
+ }
+ public Page findAllPages(int page, int size) {
+ return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending()));
+ }
+ @Transactional
+ public User addUser(UserSignUpDTO userDto){
+ final User user = new User(userDto);
+ userRepository.save(user);
+ return user;
+ }
+ @Transactional
+ public User addUser(String login, String password,
+ String passwordConfirm,
+ UserRole role){
+ if (findByLogin(login) != null) {
+ throw new ValidationException(String.format("User '%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);
+ return userRepository.save(user);
+ }
+ @Transactional(readOnly = true)
+ public User findUser(Long id) {
+ final Optional user = userRepository.findById(id);
+ return user.orElseThrow(() -> new UserNotFoundException(id));
+ }
+
+ @Transactional(readOnly = true)
+ public List findAllUsers() {
+ return userRepository.findAll();
+ }
+
+ @Transactional
+ public User updateUser(UserDto userDto) {
+ final User currentUser = findUser(userDto.getId());
+ currentUser.setLogin(userDto.getLogin());
+ currentUser.setPassword(userDto.getPassword());
+ return userRepository.save(currentUser);
+ }
+ @Transactional
+ public User updateUser(Long id,String login, String password) {
+ if (!StringUtils.hasText(login) || !StringUtils.hasText(password)) {
+ throw new IllegalArgumentException("User name, login or password is null or empty");
+ }
+ final User currentUser = findUser(id);
+ currentUser.setLogin(login);
+ currentUser.setPassword(password);
+ return userRepository.save(currentUser);
+ }
+ @Transactional
+ public void deleteUser(Long id) {
+ userRepository.deleteById(id);
+ }
+
+ @Transactional
+ public void deleteAllUsers() {
+ userRepository.deleteAll();
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ final User userEntity = findByLogin(username);
+ if (userEntity == null) {
+ throw new UsernameNotFoundException(username);
+ }
+ return new org.springframework.security.core.userdetails.User(
+ userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
+ }
+ public String loginAndGetToken(UserDto userDto) {
+ final User user = findByLogin(userDto.getLogin());
+ if (user == null) {
+ }
+ if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
+ }
+ return jwtProvider.generateToken(user.getLogin());
+ }
+ public UserDetails loadUserByToken(String token) throws UsernameNotFoundException {
+ if (!jwtProvider.isTokenValid(token)) {
+ throw new JwtException("Bad token");
+ }
+ final String userLogin = jwtProvider.getLoginFromToken(token)
+ .orElseThrow(() -> new JwtException("Token is not contain Login"));
+ return loadUserByUsername(userLogin);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java
new file mode 100644
index 0000000..e5540d0
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/PasswordEncoderConfiguration.java
@@ -0,0 +1,14 @@
+package ru.ulstu.is.sbapp;
+
+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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java
new file mode 100644
index 0000000..17fb876
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/SecurityConfiguration.java
@@ -0,0 +1,83 @@
+package ru.ulstu.is.sbapp;
+
+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.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import ru.ulstu.is.sbapp.HardwareShop.controller.UserController;
+import ru.ulstu.is.sbapp.HardwareShop.models.UserRole;
+import ru.ulstu.is.sbapp.HardwareShop.services.UserService;
+import ru.ulstu.is.sbapp.jwt.JwtFilter;
+
+@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 final UserService userService;
+ private final JwtFilter jwtFilter;
+
+ public SecurityConfiguration(UserService userService)
+ {
+ this.userService = userService;
+ this.jwtFilter = new JwtFilter(userService);
+ createAdminOnStartup();
+ }
+
+ private void createAdminOnStartup() {
+ final String admin = "admin";
+ if (userService.findByLogin(admin) == null) {
+ log.info("Admin user successfully created");
+ userService.addUser(admin, admin, admin, UserRole.ADMIN);
+ }
+ }
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.cors()
+ .and()
+ .csrf().disable()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeHttpRequests()
+ .antMatchers("/", SPA_URL_MASK).permitAll()
+ .antMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll()
+ .antMatchers(HttpMethod.POST, UserController.URL_SIGN_UP).permitAll()
+ .antMatchers(HttpMethod.POST, UserController.URL_WHO_AM_I).permitAll()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
+ .anonymous();
+ return http.userDetailsService(userService).build();
+ }
+
+ @Bean
+ public WebSecurityCustomizer webSecurityCustomizer() {
+ return (web) -> web.ignoring()
+ .antMatchers(HttpMethod.OPTIONS, "/**")
+ .antMatchers("/*.js")
+ .antMatchers("/*.html")
+ .antMatchers("/*.css")
+ .antMatchers("/assets/**")
+ .antMatchers("/favicon.ico")
+ .antMatchers("/.js", "/.css")
+ .antMatchers("/swagger-ui/index.html")
+ .antMatchers("/webjars/**")
+ .antMatchers("/swagger-resources/**")
+ .antMatchers("/v3/api-docs/**")
+ .antMatchers("/h2-console/**");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/jwt/JwtException.java b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtException.java
new file mode 100644
index 0000000..63c456c
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtException.java
@@ -0,0 +1,11 @@
+package ru.ulstu.is.sbapp.jwt;
+
+public class JwtException extends RuntimeException {
+ public JwtException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public JwtException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/jwt/JwtFilter.java b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtFilter.java
new file mode 100644
index 0000000..7cfa1bf
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtFilter.java
@@ -0,0 +1,72 @@
+package ru.ulstu.is.sbapp.jwt;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.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.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+import ru.ulstu.is.sbapp.HardwareShop.services.UserService;
+
+import java.io.IOException;
+
+public class JwtFilter extends GenericFilterBean {
+ 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;
+ }
+ }
+ }
+ chain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProperties.java b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProperties.java
new file mode 100644
index 0000000..32acfcf
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProperties.java
@@ -0,0 +1,27 @@
+package ru.ulstu.is.sbapp.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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProvider.java b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProvider.java
new file mode 100644
index 0000000..9504cb1
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/jwt/JwtProvider.java
@@ -0,0 +1,108 @@
+package ru.ulstu.is.sbapp.jwt;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.auth0.jwt.interfaces.JWTVerifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Optional;
+import java.util.UUID;
+
+@Component
+public class JwtProvider {
+ private final static Logger LOG = LoggerFactory.getLogger(JwtProvider.class);
+
+ private final static byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
+ private final static String ISSUER = "auth0";
+
+ private final Algorithm algorithm;
+ private final JWTVerifier verifier;
+
+ public JwtProvider(JwtProperties jwtProperties) {
+ if (!jwtProperties.isDev()) {
+ LOG.info("Generate new JWT key for prod");
+ try {
+ final MessageDigest salt = MessageDigest.getInstance("SHA-256");
+ salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
+ LOG.info("Use generated JWT key for prod \n{}", bytesToHex(salt.digest()));
+ algorithm = Algorithm.HMAC256(bytesToHex(salt.digest()));
+ } catch (NoSuchAlgorithmException e) {
+ throw new JwtException(e);
+ }
+ } else {
+ LOG.info("Use default JWT key for dev \n{}", jwtProperties.getDevToken());
+ algorithm = Algorithm.HMAC256(jwtProperties.getDevToken());
+ }
+ verifier = JWT.require(algorithm)
+ .withIssuer(ISSUER)
+ .build();
+ }
+
+ private static String bytesToHex(byte[] bytes) {
+ byte[] hexChars = new byte[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = HEX_ARRAY[v >>> 4];
+ hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+ }
+ return new String(hexChars, StandardCharsets.UTF_8);
+ }
+
+ public String generateToken(String login) {
+ final Date issueDate = Date.from(LocalDate.now()
+ .atStartOfDay(ZoneId.systemDefault())
+ .toInstant());
+ final Date expireDate = Date.from(LocalDate.now()
+ .plusDays(15)
+ .atStartOfDay(ZoneId.systemDefault())
+ .toInstant());
+ var temp = JWT.create();
+ var temp2 = temp.withIssuer(ISSUER);
+ var temp3 = temp2.withIssuedAt(issueDate);
+ var temp4 = temp3.withExpiresAt(expireDate);
+ var temp5 = temp4.withSubject(login);
+ var temp6 = temp5.sign(algorithm);
+ return temp6;
+ }
+
+ private DecodedJWT validateToken(String token) {
+ try {
+ return verifier.verify(token);
+ } catch (JWTVerificationException e) {
+ throw new JwtException(String.format("Token verification error: %s", e.getMessage()));
+ }
+ }
+
+ public boolean isTokenValid(String token) {
+ if (!StringUtils.hasText(token)) {
+ return false;
+ }
+ try {
+ validateToken(token);
+ return true;
+ } catch (JwtException e) {
+ LOG.error(e.getMessage());
+ return false;
+ }
+ }
+
+ public Optional getLoginFromToken(String token) {
+ try {
+ return Optional.ofNullable(validateToken(token).getSubject());
+ } catch (JwtException e) {
+ LOG.error(e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index ccc05e8..32dc422 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -8,4 +8,7 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
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
\ No newline at end of file
+spring.h2.console.settings.web-allow-others=false
+jwt.dev-token=my-secret-jwt
+jwt.dev=true
+jwt.secret = my-secret-jwt
\ No newline at end of file