Лента
@@ -187,7 +189,12 @@ export default {
methods: {
async findPosts(){
- this.posts = (await axios.get('http://localhost:8080/api/post/find?text=' + this.textFinder)).data
+ this.posts = (await axios.get('http://localhost:8080/api/1.0/post/find?text=' + this.textFinder,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ })).data
},
@@ -276,53 +283,131 @@ export default {
},
async createComment() {
- await axios.post('http://localhost:8080/comment/?text=' + this.textModal + '&ownerId=' + this.selectedProfileId + '&postId=' + this.selectedPostId);
+ await axios.post('http://localhost:8080/api/1.0/comment/?text=' + this.textModal + '&ownerId=' + this.selectedProfileId + '&postId=' + this.selectedPostId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async deleteComment(commentId) {
- await axios.delete('http://localhost:8080/comment/' + commentId);
+ await axios.delete('http://localhost:8080/api/1.0/comment/' + commentId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ );
this.refreshList();
},
async editComment(){
- await axios.put('http://localhost:8080/comment/' + this.selectedCommentId + '?text=' + this.textModal);
+ await axios.put('http://localhost:8080/api/1.0/comment/' + this.selectedCommentId + '?text=' + this.textModal,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async refreshList(){
this.profiles = [];
this.posts = [];
- const responseProfile = await axios.get('http://localhost:8080/api/profile/');
+ const responseProfile = await axios.get('http://localhost:8080/api/1.0/profile/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
responseProfile.data.forEach(element => {
this.profiles.push(element);
console.log(element);
});
- const responsePost = await axios.get('http://localhost:8080/api/post/');
+ const responsePost = await axios.get('http://localhost:8080/api/1.0/post/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
responsePost.data.forEach(element => {
this.posts.push(element);
console.log(element);
});
},
async createPost() {
- const response = await axios.post('http://localhost:8080/api/post/?text=' + this.textModal + '&authorId=' + this.selectedProfileId);
+ const response = await axios.post('http://localhost:8080/api/api/1.0/post/?text=' + this.textModal + '&authorId=' + this.selectedProfileId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async deletePost(postId) {
- const response = await axios.delete('http://localhost:8080/post/' + postId);
+ const response = await axios.delete('http://localhost:8080/api/1.0/post/' + postId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async editPost(){
- const response = await axios.put('http://localhost:8080/post/' + this.selectedPostId + '?text=' + this.textModal);
+ const response = await axios.put('http://localhost:8080/api/1.0/post/' + this.selectedPostId + '?text=' + this.textModal,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
},
async beforeMount() {
- const responseProfile = await axios.get('http://localhost:8080/api/profile/');
+
+ if (localStorage.token == null) {
+ this.$router.push(
+ {
+ name: 'login'
+ }
+ )
+ }
+
+
+ const responseProfile = await axios.get('http://localhost:8080/api/1.0/profile/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ );
responseProfile.data.forEach(element => {
this.profiles.push(element);
console.log(element);
});
- const responsePost = await axios.get('http://localhost:8080/api/post/');
+
+ const responseUser = await axios.get('http://localhost:8080/api/1.0/profile/find/' + localStorage.user,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ );
+ console.log ('bylogin')
+ console.log(responseUser.data)
+ if (responseUser != null) {
+ this.getSelectedProfile(responseUser.data)
+ }
+
+
+ const responsePost = await axios.get('http://localhost:8080/api/1.0/post/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ );
responsePost.data.forEach(element => {
this.posts.push(element);
console.log(element);
diff --git a/front/vue_front/src/components/Login.vue b/front/vue_front/src/components/Login.vue
new file mode 100644
index 0000000..6f708b3
--- /dev/null
+++ b/front/vue_front/src/components/Login.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/front/vue_front/src/components/Post.vue b/front/vue_front/src/components/Post.vue
index 072b50f..c3f4fbc 100644
--- a/front/vue_front/src/components/Post.vue
+++ b/front/vue_front/src/components/Post.vue
@@ -39,20 +39,41 @@ export default {
this.posts = profile['posts'];
},
async createPost() {
- const response = await axios.post('http://localhost:8080/api/post/?text=' + this.textModal + '&authorId=' + this.selectedProfileId);
+ const response = await axios.post('http://localhost:8080/api/1.0/post/?text=' + this.textModal + '&authorId=' + this.selectedProfileId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ );
this.refreshList();
},
async deletePost(postId) {
- const response = await axios.delete('http://localhost:8080/api/post/' + postId);
+ const response = await axios.delete('http://localhost:8080/api/1.0/post/' + postId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async editPost(){
- const response = await axios.put('http://localhost:8080/api/post/' + this.selectedPostId + '?text=' + this.textModal);
+ const response = await axios.put('http://localhost:8080/api/1.0/post/' + this.selectedPostId + '?text=' + this.textModal,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async refreshList() {
- const response = await axios.get('http://localhost:8080/api/profile/');
+ const response = await axios.get('http://localhost:8080/api/1.0/profile/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.profiles = [];
response.data.forEach(element => {
this.profiles.push(element);
@@ -70,7 +91,12 @@ export default {
this.selectedProfileId = this.$route.params.profileId
console.log(this.selectedProfileId)
- this.profile = (await axios.get('http://localhost:8080/api/profile/' + this.selectedProfileId)).data;
+ this.profile = (await axios.get('http://localhost:8080/api/1.0/profile/' + this.selectedProfileId,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ })).data;
}
}
diff --git a/front/vue_front/src/components/Profile.vue b/front/vue_front/src/components/Profile.vue
index 2a43560..2d9c4f0 100644
--- a/front/vue_front/src/components/Profile.vue
+++ b/front/vue_front/src/components/Profile.vue
@@ -89,19 +89,39 @@ export default {
},
methods: {
async createUser(){
- const response = await axios.post('http://localhost:8080/api/profile/?login=' + this.loginModal + '&password=' + this.passwordModal);
+ const response = await axios.post('http://localhost:8080/api/1.0/profile/?login=' + this.loginModal + '&password=' + this.passwordModal,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async deleteUser(id) {
- const response = await axios.delete('http://localhost:8080/api/profile/' + id);
+ const response = await axios.delete('http://localhost:8080/api/1.0/profile/' + id,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async editUser() {
- const response = await axios.put('http://localhost:8080/api/profile/' + this.selectedProfileId + '?login=' + this.loginModal + '&password=' + this.passwordModal);
+ const response = await axios.put('http://localhost:8080/api/1.0/profile/' + this.selectedProfileId + '?login=' + this.loginModal + '&password=' + this.passwordModal,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.refreshList();
},
async refreshList() {
- const response = await axios.get('http://localhost:8080/api//profile/');
+ const response = await axios.get('http://localhost:8080/api/1.0/profile/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
this.profiles = [];
response.data.forEach(element => {
this.profiles.push(element);
@@ -110,7 +130,38 @@ export default {
}
},
async beforeMount() {
- const response = await axios.get('http://localhost:8080/api/profile/');
+
+ if (localStorage.token == null) {
+ this.$router.push(
+ {
+ name: 'login'
+ }
+ )
+ }
+
+ var a = await axios.get('http://localhost:8080/api/1.0/profile/role/' + localStorage.token,
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ }
+ )
+ console.log('role')
+ console.log(a.data)
+ if (a.data != 'ADMIN'){
+ this.$router.push(
+ {
+ name: 'comments'
+ }
+ )
+ }
+
+ const response = await axios.get('http://localhost:8080/api/1.0/profile/',
+ {
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.token
+ }
+ });
response.data.forEach(element => {
this.profiles.push(element);
console.log(element);
diff --git a/front/vue_front/src/main.js b/front/vue_front/src/main.js
index 91ad5c8..f852646 100644
--- a/front/vue_front/src/main.js
+++ b/front/vue_front/src/main.js
@@ -5,6 +5,7 @@ import { createRouter, createWebHistory } from "vue-router"
import Profile from './components/Profile'
import Post from './components/Post'
import Comment from './components/Comment'
+import Login from './components/Login'
const routes = [
{
@@ -18,8 +19,14 @@ const routes = [
props: true
},
{
- path: '/comments',
+ path: '/',
+ name: 'comments',
component: Comment
+ },
+ {
+ path: '/login',
+ name: 'login',
+ component: Login
}
]
diff --git a/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java b/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java
index 0d0b1cf..9baced0 100644
--- a/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java
+++ b/src/main/java/com/webproglabs/lab1/SecurityConfiguration.java
@@ -1,6 +1,8 @@
package com.webproglabs.lab1;
+import com.webproglabs.lab1.lab34.controller.ProfileController;
import com.webproglabs.lab1.lab34.controller.mvc_controllers.UserSignupMvcController;
+import com.webproglabs.lab1.lab34.jwt.JwtFilter;
import com.webproglabs.lab1.lab34.model.UserRole;
import com.webproglabs.lab1.lab34.services.ProfileService;
import org.slf4j.Logger;
@@ -13,6 +15,8 @@ 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 org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@@ -20,10 +24,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
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 ProfileService userService;
+ private final JwtFilter jwtFilter;
public SecurityConfiguration(ProfileService userService) {
this.userService = userService;
+ this.jwtFilter = new JwtFilter(userService);
createAdminOnStartup();
}
@@ -39,7 +46,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}
}
- @Override
+/* @Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin().and()
.cors().and()
@@ -53,6 +60,24 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.loginPage(LOGIN_URL).permitAll()
.and()
.logout().permitAll();
+ }*/
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ log.info("Creating security configuration");
+ http.cors()
+ .and()
+ .csrf().disable()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeRequests()
+ .antMatchers("/", SPA_URL_MASK).permitAll()
+ .antMatchers(HttpMethod.POST, WebConfiguration.REST_API + "/profile" + ProfileController.URL_LOGIN).permitAll()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
+ .anonymous();
}
@Override
@@ -66,6 +91,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.antMatchers("/css/**")
.antMatchers("/js/**")
.antMatchers("/templates/**")
- .antMatchers("/webjars/**");
+ .antMatchers("/webjars/**")
+ .antMatchers("/swagger-resources/**")
+ .antMatchers("/v3/api-docs/**");
}
}
\ No newline at end of file
diff --git a/src/main/java/com/webproglabs/lab1/WebConfiguration.java b/src/main/java/com/webproglabs/lab1/WebConfiguration.java
index ef50db9..612b2b6 100644
--- a/src/main/java/com/webproglabs/lab1/WebConfiguration.java
+++ b/src/main/java/com/webproglabs/lab1/WebConfiguration.java
@@ -1,18 +1,32 @@
package com.webproglabs.lab1;
+import com.webproglabs.lab1.lab34.OpenAPI30Configuration;
+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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
- public static final String REST_API = "/api";
+ public static final String REST_API = OpenAPI30Configuration.API_PREFIX;
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("login");
+ registry.addViewController(SecurityConfiguration.SPA_URL_MASK).setViewName("forward:/");
+ registry.addViewController("/notFound").setViewName("forward:/");
}
+
+ @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/com/webproglabs/lab1/lab34/OpenAPI30Configuration.java b/src/main/java/com/webproglabs/lab1/lab34/OpenAPI30Configuration.java
new file mode 100644
index 0000000..0b5909f
--- /dev/null
+++ b/src/main/java/com/webproglabs/lab1/lab34/OpenAPI30Configuration.java
@@ -0,0 +1,28 @@
+package com.webproglabs.lab1.lab34;
+
+import com.webproglabs.lab1.lab34.jwt.JwtFilter;
+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;
+
+@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/com/webproglabs/lab1/lab34/controller/ProfileController.java b/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileController.java
index a0f80cc..a2e9e5c 100644
--- a/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileController.java
+++ b/src/main/java/com/webproglabs/lab1/lab34/controller/ProfileController.java
@@ -1,15 +1,21 @@
package com.webproglabs.lab1.lab34.controller;
import com.webproglabs.lab1.WebConfiguration;
+import com.webproglabs.lab1.lab34.model.Profile;
+import com.webproglabs.lab1.lab34.model.UserRole;
import com.webproglabs.lab1.lab34.services.ProfileService;
import org.springframework.web.bind.annotation.*;
+import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/profile")
public class ProfileController {
+
+ public static final String URL_LOGIN = "/jwt/login";
+
private final ProfileService profileService;
public ProfileController(ProfileService profileService) {
@@ -21,6 +27,11 @@ public class ProfileController {
return new ProfileDto(profileService.findUser(id));
}
+ @GetMapping("/find/{login}")
+ public ProfileDto getProfileByLogin(@PathVariable String login) {
+ return new ProfileDto(profileService.findByLogin(login));
+ }
+
@GetMapping
public List getProfiles() {
return profileService.findAllUsers().stream()
@@ -55,4 +66,23 @@ public class ProfileController {
public void deleteAllProfiles() {
profileService.deleteAllUsers();
}
+ @PostMapping(URL_LOGIN)
+ public String login(@RequestBody @Valid ProfileDto userDto) {
+ return profileService.loginAndGetToken(userDto);
+ }
+
+
+ // этот метод юзать для проверки на администраторсткую роль вместо старой проверки по логину админа ЛОЛ
+ //
+ @GetMapping("role/{token}")
+ public String getRoleByToken(@PathVariable String token) {
+ var userDetails = profileService.loadUserByToken(token);
+ Profile user = profileService.findByLogin(userDetails.getUsername());
+ if (user != null) {
+ return user.getRole().toString();
+ }
+ return null;
+ }
+
+
}
diff --git a/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtException.java b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtException.java
new file mode 100644
index 0000000..bdc8572
--- /dev/null
+++ b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtException.java
@@ -0,0 +1,11 @@
+package com.webproglabs.lab1.lab34.jwt;
+
+public class JwtException extends RuntimeException {
+ public JwtException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public JwtException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtFilter.java b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtFilter.java
new file mode 100644
index 0000000..ad981bb
--- /dev/null
+++ b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtFilter.java
@@ -0,0 +1,72 @@
+package com.webproglabs.lab1.lab34.jwt;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.webproglabs.lab1.lab34.services.ProfileService;
+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 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 ProfileService userService;
+
+ public JwtFilter(ProfileService 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/com/webproglabs/lab1/lab34/jwt/JwtProperties.java b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtProperties.java
new file mode 100644
index 0000000..e51cfd2
--- /dev/null
+++ b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtProperties.java
@@ -0,0 +1,27 @@
+package com.webproglabs.lab1.lab34.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/com/webproglabs/lab1/lab34/jwt/JwtProvider.java b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtProvider.java
new file mode 100644
index 0000000..2e53566
--- /dev/null
+++ b/src/main/java/com/webproglabs/lab1/lab34/jwt/JwtProvider.java
@@ -0,0 +1,107 @@
+package com.webproglabs.lab1.lab34.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/com/webproglabs/lab1/lab34/services/ProfileService.java b/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java
index 7a5febd..d205366 100644
--- a/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java
+++ b/src/main/java/com/webproglabs/lab1/lab34/services/ProfileService.java
@@ -1,5 +1,8 @@
package com.webproglabs.lab1.lab34.services;
+import com.webproglabs.lab1.lab34.controller.ProfileDto;
+import com.webproglabs.lab1.lab34.jwt.JwtException;
+import com.webproglabs.lab1.lab34.jwt.JwtProvider;
import com.webproglabs.lab1.lab34.model.Comment;
import com.webproglabs.lab1.lab34.model.Post;
import com.webproglabs.lab1.lab34.model.Profile;
@@ -22,9 +25,12 @@ public class ProfileService implements UserDetailsService {
private final ProfileRepository profileRepository;
private final PasswordEncoder passwordEncoder;
- public ProfileService(ProfileRepository profileRepository, PasswordEncoder passwordEncoder) {
+ private final JwtProvider jwtProvider;
+
+ public ProfileService(ProfileRepository profileRepository, PasswordEncoder passwordEncoder, JwtProvider jwtProvider) {
this.profileRepository = profileRepository;
this.passwordEncoder = passwordEncoder;
+ this.jwtProvider = jwtProvider;
}
@Transactional
@@ -121,4 +127,32 @@ public class ProfileService implements UserDetailsService {
return new org.springframework.security.core.userdetails.User(
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
}
+
+ public String loginAndGetToken(ProfileDto userDto) {
+
+ try {
+ final Profile user = findByLogin(userDto.getLogin());
+ if (user == null) {
+ throw new Exception("Login not found" + userDto.getLogin());
+ }
+ if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
+ throw new Exception("User not found" + user.getLogin());
+ }
+ return jwtProvider.generateToken(user.getLogin());
+ }
+ catch (Exception e) {
+ var ex = e;
+ return null;
+ }
+
+ }
+
+ 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);
+ }
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8bb0d85..6455a46 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -11,3 +11,5 @@ spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
# Security
spring.security.user.password=user
+jwt.dev-token=my-secret-jwt
+jwt.dev=true