diff --git a/build.gradle b/build.gradle
index 36cc235..24b4a7f 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 3abbc24..b0c13a3 100644
Binary files a/data.mv.db and b/data.mv.db differ
diff --git a/front/src/components/pages/Login.jsx b/front/src/components/pages/Login.jsx
new file mode 100644
index 0000000..cbbdf12
--- /dev/null
+++ b/front/src/components/pages/Login.jsx
@@ -0,0 +1,76 @@
+import { useState } from "react"
+import { NavLink, useNavigate } from "react-router-dom";
+import './form.css';
+
+
+export default function Film() {
+ const [data, setData] = useState({
+ login: "",
+ password: ""
+ });
+ const navigate = useNavigate();
+
+ const [isError, setError] = useState(false);
+
+ const handleFormChange = (e) => {
+ setData({ ...data, [e.target.id]: e.target.value });
+ }
+
+ const formSubmit = async (e) => {
+ e.preventDefault();
+ const requestParams = {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ login: data.login, password: data.password }),
+ };
+ const response = await fetch("http://localhost:8080/jwt/login", requestParams);
+ const result = await response.text();
+ if (response.status === 200) {
+ setError(false);
+ localStorage.setItem("token", result);
+ localStorage.setItem("user", data.login);
+ getRole(result);
+ } else {
+ setError(true);
+ localStorage.removeItem("token");
+ localStorage.removeItem("user");
+ localStorage.removeItem("role");
+ }
+ }
+
+ const getRole = async function (token) {
+ const requestParams = {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ };
+ const requestUrl = `http://localhost:8080/user?token=${token}`;
+ const response = await fetch(requestUrl, requestParams);
+ const result = await response.text();
+ localStorage.setItem("role", result);
+ window.dispatchEvent(new Event("storage"));
+ navigate("/");
+ }
+
+ return (
+ <>
+ {isError &&
+ Неверный логин или пароль
+
}
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java
new file mode 100644
index 0000000..5274b74
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/OpenAPI30Configuration.java
@@ -0,0 +1,29 @@
+package ru.ulstu.is.sbapp.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.ulstu.is.sbapp.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/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java
index fadfa18..3849ce9 100644
--- a/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java
+++ b/src/main/java/ru/ulstu/is/sbapp/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/ulstu/is/sbapp/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java
index 3851f07..664fa9a 100644
--- a/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java
@@ -5,26 +5,29 @@ 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.ulstu.is.sbapp.socialNetwork.controller.UserSignupMvcController;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import ru.ulstu.is.sbapp.configuration.jwt.JwtFilter;
+import ru.ulstu.is.sbapp.socialNetwork.controller.MyUserController;
+import ru.ulstu.is.sbapp.socialNetwork.controller.UserController;
import ru.ulstu.is.sbapp.socialNetwork.models.UserRole;
-import ru.ulstu.is.sbapp.socialNetwork.services.MyUserService;
import ru.ulstu.is.sbapp.socialNetwork.services.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();
}
@@ -38,31 +41,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/ulstu/is/sbapp/configuration/WebConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java
index 91f2516..c46ca53 100644
--- a/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java
@@ -1,6 +1,11 @@
package ru.ulstu.is.sbapp.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;
@@ -8,12 +13,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/**").allowedMethods("*");
- }
public static final String REST_API = "/api";
-
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
@@ -22,9 +22,19 @@ 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("*");
+ }
+
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/css/**").addResourceLocations("/static/css/");
}
-
}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtException.java b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtException.java
new file mode 100644
index 0000000..ba2c2f5
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtException.java
@@ -0,0 +1,11 @@
+package ru.ulstu.is.sbapp.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/ulstu/is/sbapp/configuration/jwt/JwtFilter.java b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtFilter.java
new file mode 100644
index 0000000..833e741
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtFilter.java
@@ -0,0 +1,72 @@
+package ru.ulstu.is.sbapp.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.ulstu.is.sbapp.socialNetwork.services.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/ulstu/is/sbapp/configuration/jwt/JwtProperties.java b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtProperties.java
new file mode 100644
index 0000000..d93ff16
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtProperties.java
@@ -0,0 +1,27 @@
+package ru.ulstu.is.sbapp.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/ulstu/is/sbapp/configuration/jwt/JwtProvider.java b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtProvider.java
new file mode 100644
index 0000000..9d3db53
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/configuration/jwt/JwtProvider.java
@@ -0,0 +1,107 @@
+package ru.ulstu.is.sbapp.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/ulstu/is/sbapp/socialNetwork/controller/MyUserController.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/MyUserController.java
new file mode 100644
index 0000000..dcc715e
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/MyUserController.java
@@ -0,0 +1,68 @@
+package ru.ulstu.is.sbapp.socialNetwork.controller;
+
+import org.springframework.web.bind.annotation.*;
+import ru.ulstu.is.sbapp.configuration.WebConfiguration;
+import ru.ulstu.is.sbapp.socialNetwork.dto.MyUserDTO;
+import ru.ulstu.is.sbapp.socialNetwork.models.UserModel;
+import ru.ulstu.is.sbapp.socialNetwork.services.MyUserService;
+
+
+import javax.validation.Valid;
+import java.util.List;
+
+
+@RestController
+@RequestMapping(WebConfiguration.REST_API + "/users")
+public class MyUserController {
+ private final MyUserService userService;
+
+ public MyUserController(MyUserService userService){
+ this.userService = userService;
+ }
+
+ @GetMapping("/{id}")
+ public MyUserDTO getUser(@PathVariable Long id) {
+ return new MyUserDTO(userService.findUser(id));
+ }
+
+ @GetMapping("")
+ public List getUsers() {
+ return userService.findAllUsers().stream().map(MyUserDTO::new).toList();
+ }
+
+ @PostMapping("")
+ public MyUserDTO createUser(@RequestBody @Valid MyUserDTO user) {
+ UserModel result = userService.addUser(user.getName(), user.getCity());
+ userService.updateCommunities(result.getId(), user.getCommunity());
+ return new MyUserDTO(userService.updateMusics(result.getId(), user.getMusic()));
+ }
+
+ @PatchMapping("/{id}")
+ public MyUserDTO updateUser(@PathVariable Long id,
+ @RequestBody @Valid MyUserDTO user) {
+ UserModel result = userService.updateUser(id, user.getName());
+ userService.updateCommunities(result.getId(), user.getCommunity());
+ return new MyUserDTO(userService.updateMusics(result.getId(), user.getMusic()));
+ }
+
+ @PatchMapping("/add_music/{id}")
+ public MyUserDTO addMusic(@PathVariable Long id, @RequestParam Long music_id) {
+ return new MyUserDTO(userService.addMusic(id, music_id));
+ }
+
+ @PatchMapping("/add_community/{id}")
+ public MyUserDTO addCommunity(@PathVariable Long id, @RequestParam Long community_id) {
+ return new MyUserDTO(userService.addCommunity(id, community_id));
+ }
+ @GetMapping("/search")
+ public List searchUsers(@RequestParam("city") String city) {
+ List users = userService.findUserByCity(city);
+ return users.stream().map(MyUserDTO::new).toList();
+ }
+
+ @DeleteMapping("/{id}")
+ public MyUserDTO deleteUser(@PathVariable Long id) {
+ return new MyUserDTO(userService.deleteUser(id));
+ }
+
+}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserController.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserController.java
index 7f422c0..56a961f 100644
--- a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserController.java
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserController.java
@@ -1,68 +1,58 @@
package ru.ulstu.is.sbapp.socialNetwork.controller;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
-import ru.ulstu.is.sbapp.configuration.WebConfiguration;
-import ru.ulstu.is.sbapp.socialNetwork.dto.MyUserDTO;
-import ru.ulstu.is.sbapp.socialNetwork.models.UserModel;
-import ru.ulstu.is.sbapp.socialNetwork.services.MyUserService;
-
+import ru.ulstu.is.sbapp.socialNetwork.dto.UserDto;
+import ru.ulstu.is.sbapp.socialNetwork.dto.UserInfoDTO;
+import ru.ulstu.is.sbapp.socialNetwork.dto.UsersPageDTO;
+import ru.ulstu.is.sbapp.socialNetwork.models.User;
+import ru.ulstu.is.sbapp.socialNetwork.models.UserRole;
+import ru.ulstu.is.sbapp.socialNetwork.services.UserService;
import javax.validation.Valid;
import java.util.List;
-
+import java.util.stream.IntStream;
@RestController
-@RequestMapping(WebConfiguration.REST_API + "/users")
public class UserController {
- private final MyUserService userService;
+ public static final String URL_LOGIN = "/jwt/login";
+ public static final String URL_SIGNUP = "/jwt/signup";
- public UserController(MyUserService userService){
+ private final UserService userService;
+
+ public UserController(UserService userService) {
this.userService = userService;
}
- @GetMapping("/{id}")
- public MyUserDTO getUser(@PathVariable Long id) {
- return new MyUserDTO(userService.findUser(id));
+ @GetMapping("/user")
+ public String findUser(@RequestParam("token") String token) {
+ UserDetails userDetails = userService.loadUserByToken(token);
+ User user = userService.findByLogin(userDetails.getUsername());
+ return user.getRole().toString();
+ }
+ @GetMapping("/users")
+ @Secured({UserRole.AsString.ADMIN})
+ public UsersPageDTO getUsers(@RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "5") int size) {
+ final Page users = userService.findAllPages(page, size)
+ .map(UserDto::new);
+ final int totalPages = users.getTotalPages();
+ final List pageNumbers = IntStream.rangeClosed(1, totalPages)
+ .boxed()
+ .toList();
+ return new UsersPageDTO(users, pageNumbers, totalPages);
}
- @GetMapping("")
- public List getUsers() {
- return userService.findAllUsers().stream().map(MyUserDTO::new).toList();
+
+ @PostMapping(URL_SIGNUP)
+ public UserInfoDTO signup(@RequestBody @Valid UserDto userDto) {
+ return userService.signupAndGetToken(userDto);
}
- @PostMapping("")
- public MyUserDTO createUser(@RequestBody @Valid MyUserDTO user) {
- UserModel result = userService.addUser(user.getName(), user.getCity());
- userService.updateCommunities(result.getId(), user.getCommunity());
- return new MyUserDTO(userService.updateMusics(result.getId(), user.getMusic()));
+ @PostMapping(URL_LOGIN)
+ public String login(@RequestBody @Valid UserDto userDto) {
+ return userService.loginAndGetToken(userDto);
}
-
- @PatchMapping("/{id}")
- public MyUserDTO updateUser(@PathVariable Long id,
- @RequestBody @Valid MyUserDTO user) {
- UserModel result = userService.updateUser(id, user.getName());
- userService.updateCommunities(result.getId(), user.getCommunity());
- return new MyUserDTO(userService.updateMusics(result.getId(), user.getMusic()));
- }
-
- @PatchMapping("/add_music/{id}")
- public MyUserDTO addMusic(@PathVariable Long id, @RequestParam Long music_id) {
- return new MyUserDTO(userService.addMusic(id, music_id));
- }
-
- @PatchMapping("/add_community/{id}")
- public MyUserDTO addCommunity(@PathVariable Long id, @RequestParam Long community_id) {
- return new MyUserDTO(userService.addCommunity(id, community_id));
- }
- @GetMapping("/search")
- public List searchUsers(@RequestParam("city") String city) {
- List users = userService.findUserByCity(city);
- return users.stream().map(MyUserDTO::new).toList();
- }
-
- @DeleteMapping("/{id}")
- public MyUserDTO deleteUser(@PathVariable Long id) {
- return new MyUserDTO(userService.deleteUser(id));
- }
-
}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserMvcController.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserMvcController.java
deleted file mode 100644
index 54a600b..0000000
--- a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/controller/UserMvcController.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package ru.ulstu.is.sbapp.socialNetwork.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.ulstu.is.sbapp.socialNetwork.dto.UserDto;
-import ru.ulstu.is.sbapp.socialNetwork.models.UserRole;
-import ru.ulstu.is.sbapp.socialNetwork.services.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/ulstu/is/sbapp/socialNetwork/dto/UserDto.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserDto.java
index 6bfc837..e302517 100644
--- a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserDto.java
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserDto.java
@@ -3,25 +3,35 @@ package ru.ulstu.is.sbapp.socialNetwork.dto;
import ru.ulstu.is.sbapp.socialNetwork.models.User;
import ru.ulstu.is.sbapp.socialNetwork.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 UserRole role;
+ private String passwordConfirm;
public UserDto(User user) {
- this.id = user.getId();
this.login = user.getLogin();
+ this.password = user.getPassword();
this.role = user.getRole();
}
- public long getId() {
- return id;
- }
+ public UserDto() {}
public String getLogin() {
return login;
}
+ public String getPassword() {
+ return password;
+ }
+ public String getPasswordConfirm() {
+ return passwordConfirm;
+ }
+
public UserRole getRole() {
return role;
}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserInfoDTO.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserInfoDTO.java
new file mode 100644
index 0000000..5b027d7
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UserInfoDTO.java
@@ -0,0 +1,27 @@
+package ru.ulstu.is.sbapp.socialNetwork.dto;
+
+
+import ru.ulstu.is.sbapp.socialNetwork.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/ulstu/is/sbapp/socialNetwork/dto/UsersPageDTO.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UsersPageDTO.java
new file mode 100644
index 0000000..6983462
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/dto/UsersPageDTO.java
@@ -0,0 +1,29 @@
+package ru.ulstu.is.sbapp.socialNetwork.dto;
+
+import org.springframework.data.domain.Page;
+
+import java.util.List;
+
+public class UsersPageDTO {
+ private Page users;
+ private List pageNumbers;
+ private int totalPages;
+
+ public UsersPageDTO(Page users, List pageNumbers, int totalPages) {
+ this.users = users;
+ this.pageNumbers = pageNumbers;
+ this.totalPages = totalPages;
+ }
+
+ public Page getUsers() {
+ return users;
+ }
+
+ public List getPageNumbers() {
+ return pageNumbers;
+ }
+
+ public int getTotalPages() {
+ return totalPages;
+ }
+}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserExistsException.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserExistsException.java
new file mode 100644
index 0000000..ab097de
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserExistsException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.is.sbapp.socialNetwork.services;
+
+public class UserExistsException extends RuntimeException {
+ public UserExistsException(String login) {
+ super(String.format("User '%s' already exists", login));
+ }
+}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserNotFoundException.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserNotFoundException.java
new file mode 100644
index 0000000..f5a2745
--- /dev/null
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserNotFoundException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.is.sbapp.socialNetwork.services;
+
+public class UserNotFoundException extends RuntimeException {
+ public UserNotFoundException(String login) {
+ super(String.format("User not found '%s'", login));
+ }
+}
diff --git a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserService.java b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserService.java
index e9d1112..f8bd5f6 100644
--- a/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/UserService.java
+++ b/src/main/java/ru/ulstu/is/sbapp/socialNetwork/services/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.ulstu.is.sbapp.configuration.jwt.JwtException;
+import ru.ulstu.is.sbapp.configuration.jwt.JwtProvider;
+import ru.ulstu.is.sbapp.socialNetwork.dto.UserDto;
+import ru.ulstu.is.sbapp.socialNetwork.dto.UserInfoDTO;
import ru.ulstu.is.sbapp.socialNetwork.models.User;
import ru.ulstu.is.sbapp.socialNetwork.models.UserRole;
import ru.ulstu.is.sbapp.socialNetwork.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