diff --git a/build.gradle b/build.gradle index 8fd4e4b..dbeed0f 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' + implementation 'com.auth0:java-jwt:4.4.0' implementation 'org.springdoc:springdoc-openapi-ui:1.6.5' diff --git a/data.mv.db b/data.mv.db index 2c4a847..6f6e554 100644 Binary files a/data.mv.db and b/data.mv.db differ diff --git a/src/main/java/ru/ip/labs/labs/configuration/OpenAPI30Configuration.java b/src/main/java/ru/ip/labs/labs/configuration/OpenAPI30Configuration.java new file mode 100644 index 0000000..ae48092 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/configuration/OpenAPI30Configuration.java @@ -0,0 +1,28 @@ +package ru.ip.labs.labs.configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.ip.labs.labs.configuration.jwt.JwtFilter; + +@Configuration +public class OpenAPI30Configuration { + public static final String API_PREFIX = "/api/1.0"; + + @Bean + public OpenAPI customizeOpenAPI() { + final String securitySchemeName = JwtFilter.TOKEN_BEGIN_STR; + return new OpenAPI() + .addSecurityItem(new SecurityRequirement() + .addList(securitySchemeName)) + .components(new Components() + .addSecuritySchemes(securitySchemeName, new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } +} diff --git a/src/main/java/ru/ip/labs/labs/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ip/labs/labs/configuration/PasswordEncoderConfiguration.java index 5aef121..fa789c4 100644 --- a/src/main/java/ru/ip/labs/labs/configuration/PasswordEncoderConfiguration.java +++ b/src/main/java/ru/ip/labs/labs/configuration/PasswordEncoderConfiguration.java @@ -8,7 +8,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class PasswordEncoderConfiguration { @Bean - public PasswordEncoder createPasswordEncoder() { + public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } diff --git a/src/main/java/ru/ip/labs/labs/configuration/SecurityConfiguration.java b/src/main/java/ru/ip/labs/labs/configuration/SecurityConfiguration.java index ec3ec4a..6f37104 100644 --- a/src/main/java/ru/ip/labs/labs/configuration/SecurityConfiguration.java +++ b/src/main/java/ru/ip/labs/labs/configuration/SecurityConfiguration.java @@ -5,25 +5,28 @@ import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import ru.ip.labs.labs.films.controller.UserSignupMvcController; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import ru.ip.labs.labs.configuration.jwt.JwtFilter; +import ru.ip.labs.labs.films.controller.UserController; import ru.ip.labs.labs.films.models.UserRole; import ru.ip.labs.labs.films.service.UserService; @Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 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(); } @@ -37,31 +40,37 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http.headers().frameOptions().sameOrigin().and() - .cors().and() + log.info("Creating security configuration"); + http.cors() + .and() .csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() .authorizeRequests() - .antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll() - .antMatchers(HttpMethod.GET, LOGIN_URL).permitAll() - .anyRequest().authenticated() + .antMatchers("/", SPA_URL_MASK).permitAll() + .antMatchers(HttpMethod.POST, UserController.URL_SIGNUP).permitAll() + .antMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll() + .anyRequest() + .authenticated() .and() - .formLogin() - .loginPage(LOGIN_URL).permitAll() - .and() - .logout().permitAll(); + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .anonymous(); } @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(userService); + protected void configure(AuthenticationManagerBuilder builder) throws Exception { + builder.userDetailsService(userService); } @Override public void configure(WebSecurity web) { - web.ignoring() - .antMatchers("/css/**") - .antMatchers("/js/**") - .antMatchers("/templates/**") - .antMatchers("/webjars/**"); + web + .ignoring() + .antMatchers(HttpMethod.OPTIONS, "/**") + .antMatchers("/**/*.{js,html,css,png}") + .antMatchers("/swagger-ui/index.html") + .antMatchers("/webjars/**") + .antMatchers("/swagger-resources/**") + .antMatchers("/v3/api-docs/**"); } -} \ No newline at end of file +} diff --git a/src/main/java/ru/ip/labs/labs/configuration/WebConfiguration.java b/src/main/java/ru/ip/labs/labs/configuration/WebConfiguration.java index 56c9ee5..9d34d4e 100644 --- a/src/main/java/ru/ip/labs/labs/configuration/WebConfiguration.java +++ b/src/main/java/ru/ip/labs/labs/configuration/WebConfiguration.java @@ -1,6 +1,11 @@ package ru.ip.labs.labs.configuration; +import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; @@ -17,6 +22,12 @@ public class WebConfiguration implements WebMvcConfigurer { registry.addViewController("catalogs"); registry.addViewController("login"); } + + @Bean + public WebServerFactoryCustomizer containerCustomizer() { + return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notFound")); + } + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("*"); diff --git a/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtException.java b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtException.java new file mode 100644 index 0000000..be842ff --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtException.java @@ -0,0 +1,11 @@ +package ru.ip.labs.labs.configuration.jwt; + +public class JwtException extends RuntimeException { + public JwtException(Throwable throwable) { + super(throwable); + } + + public JwtException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtFilter.java b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtFilter.java new file mode 100644 index 0000000..162a794 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtFilter.java @@ -0,0 +1,72 @@ +package ru.ip.labs.labs.configuration.jwt; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.ip.labs.labs.films.service.UserService; + +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 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); + } +} diff --git a/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProperties.java b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProperties.java new file mode 100644 index 0000000..a55c0c9 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProperties.java @@ -0,0 +1,27 @@ +package ru.ip.labs.labs.configuration.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/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProvider.java b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProvider.java new file mode 100644 index 0000000..8b7694f --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/configuration/jwt/JwtProvider.java @@ -0,0 +1,107 @@ +package ru.ip.labs.labs.configuration.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()); + return JWT.create() + .withIssuer(ISSUER) + .withIssuedAt(issueDate) + .withExpiresAt(expireDate) + .withSubject(login) + .sign(algorithm); + } + + 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(); + } + } +} diff --git a/src/main/java/ru/ip/labs/labs/films/controller/UserController.java b/src/main/java/ru/ip/labs/labs/films/controller/UserController.java new file mode 100644 index 0000000..1ffbffc --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/films/controller/UserController.java @@ -0,0 +1,39 @@ +package ru.ip.labs.labs.films.controller; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; +import ru.ip.labs.labs.films.dto.UserDto; +import ru.ip.labs.labs.films.dto.UserInfoDTO; +import ru.ip.labs.labs.films.models.User; +import ru.ip.labs.labs.films.service.UserService; + +import javax.validation.Valid; + +@RestController +public class UserController { + public static final String URL_LOGIN = "/jwt/login"; + public static final String URL_SIGNUP = "/jwt/signup"; + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/user") + public String findUser(@RequestParam("token") String token) { + UserDetails userDetails = userService.loadUserByToken(token); + User user = userService.findByLogin(userDetails.getUsername()); + return user.getRole().toString(); + } + + @PostMapping(URL_SIGNUP) + public UserInfoDTO signup(@RequestBody @Valid UserDto userDto) { + return userService.signupAndGetToken(userDto); + } + + @PostMapping(URL_LOGIN) + public String login(@RequestBody @Valid UserDto userDto) { + return userService.loginAndGetToken(userDto); + } +} diff --git a/src/main/java/ru/ip/labs/labs/films/controller/UserMvcController.java b/src/main/java/ru/ip/labs/labs/films/controller/UserMvcController.java deleted file mode 100644 index e8dfc78..0000000 --- a/src/main/java/ru/ip/labs/labs/films/controller/UserMvcController.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.ip.labs.labs.films.controller; - -import org.springframework.data.domain.Page; -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; -import org.springframework.web.bind.annotation.RequestParam; -import ru.ip.labs.labs.films.dto.UserDto; -import ru.ip.labs.labs.films.models.UserRole; -import ru.ip.labs.labs.films.service.UserService; - -import java.util.List; -import java.util.stream.IntStream; - -@Controller -@RequestMapping("/users") -public class UserMvcController { - private final UserService userService; - - public UserMvcController(UserService userService) { - this.userService = userService; - } - - @GetMapping - @Secured({UserRole.AsString.ADMIN}) - public String getUsers(@RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "5") int size, - Model model) { - final Page users = userService.findAllPages(page, size) - .map(UserDto::new); - model.addAttribute("users", users); - final int totalPages = users.getTotalPages(); - final List pageNumbers = IntStream.rangeClosed(1, totalPages) - .boxed() - .toList(); - model.addAttribute("pages", pageNumbers); - model.addAttribute("totalPages", totalPages); - return "users"; - } -} diff --git a/src/main/java/ru/ip/labs/labs/films/controller/UserSignupMvcController.java b/src/main/java/ru/ip/labs/labs/films/controller/UserSignupMvcController.java deleted file mode 100644 index bb842b5..0000000 --- a/src/main/java/ru/ip/labs/labs/films/controller/UserSignupMvcController.java +++ /dev/null @@ -1,51 +0,0 @@ -package ru.ip.labs.labs.films.controller; - -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; -import ru.ip.labs.labs.films.models.User; -import ru.ip.labs.labs.films.models.UserSignupDto; -import ru.ip.labs.labs.films.service.UserService; -import ru.ip.labs.labs.films.util.validation.ValidationException; - -import javax.validation.Valid; - -@Controller -@RequestMapping(UserSignupMvcController.SIGNUP_URL) -public class UserSignupMvcController { - public static final String SIGNUP_URL = "/signup"; - - private final UserService userService; - - public UserSignupMvcController(UserService userService) { - this.userService = userService; - } - - @GetMapping - public String showSignupForm(Model model) { - model.addAttribute("userDto", new UserSignupDto()); - return "signup"; - } - - @PostMapping - public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto, - BindingResult bindingResult, - Model model) { - if (bindingResult.hasErrors()) { - model.addAttribute("errors", bindingResult.getAllErrors()); - return "signup"; - } - try { - final User user = userService.createUser( - userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm()); - return "redirect:/login?created=" + user.getLogin(); - } catch (ValidationException e) { - model.addAttribute("errors", e.getMessage()); - return "signup"; - } - } -} diff --git a/src/main/java/ru/ip/labs/labs/films/dto/UserDto.java b/src/main/java/ru/ip/labs/labs/films/dto/UserDto.java index 7c20374..27ac8b1 100644 --- a/src/main/java/ru/ip/labs/labs/films/dto/UserDto.java +++ b/src/main/java/ru/ip/labs/labs/films/dto/UserDto.java @@ -1,28 +1,24 @@ package ru.ip.labs.labs.films.dto; -import ru.ip.labs.labs.films.models.User; -import ru.ip.labs.labs.films.models.UserRole; +import javax.validation.constraints.NotEmpty; public class UserDto { - private final long id; - private final String login; - private final UserRole role; + @NotEmpty + private String login; + @NotEmpty + private String password; + private String passwordConfirm; - public UserDto(User user) { - this.id = user.getId(); - this.login = user.getLogin(); - this.role = user.getRole(); - } - - public long getId() { - return id; - } public String getLogin() { return login; } - public UserRole getRole() { - return role; + public String getPassword() { + return password; } + public String getPasswordConfirm() { + return passwordConfirm; + } + } diff --git a/src/main/java/ru/ip/labs/labs/films/dto/UserInfoDTO.java b/src/main/java/ru/ip/labs/labs/films/dto/UserInfoDTO.java new file mode 100644 index 0000000..24b8ad3 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/films/dto/UserInfoDTO.java @@ -0,0 +1,27 @@ +package ru.ip.labs.labs.films.dto; + + +import ru.ip.labs.labs.films.models.UserRole; + +public class UserInfoDTO { + private final String token; + private String login; + private final UserRole role; + public UserInfoDTO(String token, String login, UserRole role) { + this.token = token; + this.login = login; + this.role = role; + } + + public String getToken() { + return token; + } + + public UserRole getRole() { + + return role; + } + public String getLogin() { + return login; + } +} diff --git a/src/main/java/ru/ip/labs/labs/films/service/UserExistsException.java b/src/main/java/ru/ip/labs/labs/films/service/UserExistsException.java new file mode 100644 index 0000000..503c671 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/films/service/UserExistsException.java @@ -0,0 +1,7 @@ +package ru.ip.labs.labs.films.service; + +public class UserExistsException extends RuntimeException { + public UserExistsException(String login) { + super(String.format("User '%s' already exists", login)); + } +} diff --git a/src/main/java/ru/ip/labs/labs/films/service/UserNotFoundException.java b/src/main/java/ru/ip/labs/labs/films/service/UserNotFoundException.java new file mode 100644 index 0000000..77f8603 --- /dev/null +++ b/src/main/java/ru/ip/labs/labs/films/service/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ip.labs.labs.films.service; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String login) { + super(String.format("User not found '%s'", login)); + } +} diff --git a/src/main/java/ru/ip/labs/labs/films/service/UserService.java b/src/main/java/ru/ip/labs/labs/films/service/UserService.java index 247031b..3cce4d8 100644 --- a/src/main/java/ru/ip/labs/labs/films/service/UserService.java +++ b/src/main/java/ru/ip/labs/labs/films/service/UserService.java @@ -8,6 +8,10 @@ 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 ru.ip.labs.labs.configuration.jwt.JwtException; +import ru.ip.labs.labs.configuration.jwt.JwtProvider; +import ru.ip.labs.labs.films.dto.UserDto; +import ru.ip.labs.labs.films.dto.UserInfoDTO; import ru.ip.labs.labs.films.models.User; import ru.ip.labs.labs.films.models.UserRole; import ru.ip.labs.labs.films.repository.UserRepository; @@ -22,13 +26,16 @@ public class UserService implements UserDetailsService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final ValidatorUtil validatorUtil; + private final JwtProvider jwtProvider; public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, - ValidatorUtil validatorUtil) { + ValidatorUtil validatorUtil, + JwtProvider jwtProvider) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.validatorUtil = validatorUtil; + this.jwtProvider = jwtProvider; } public Page findAllPages(int page, int size) { @@ -45,7 +52,7 @@ public class UserService implements UserDetailsService { public User createUser(String login, String password, String passwordConfirm, UserRole role) { if (findByLogin(login) != null) { - throw new ValidationException(String.format("User '%s' already exists", login)); + throw new UserExistsException(login); } final User user = new User(login, passwordEncoder.encode(password), role); validatorUtil.validate(user); @@ -55,6 +62,26 @@ public class UserService implements UserDetailsService { return userRepository.save(user); } + public String loginAndGetToken(UserDto userDto) { + final User user = findByLogin(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()); + } + + 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); + } + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { final User userEntity = findByLogin(username); @@ -64,4 +91,9 @@ public class UserService implements UserDetailsService { return new org.springframework.security.core.userdetails.User( userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole())); } -} + + public UserInfoDTO signupAndGetToken(UserDto userDto) { + final User user = createUser(userDto.getLogin(), userDto.getPassword(), userDto.getPasswordConfirm(), UserRole.USER); + return new UserInfoDTO(jwtProvider.generateToken(user.getLogin()), user.getLogin(), UserRole.USER); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index da7b0b1..055f74b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,3 +9,5 @@ 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