From 6101ef5ded1122b901a7ed57616c0bb2d9ad3d98 Mon Sep 17 00:00:00 2001 From: Joao Pedro Truchinski Borba Date: Sat, 15 Jun 2024 20:49:34 -0300 Subject: [PATCH 1/4] feat: jwt auth --- pom.xml | 32 ++++++++++- .../java/com/health/app/AppApplication.java | 2 + .../com/health/app/config/SecurityConfig.java | 27 +++++++++ .../java/com/health/app/config/WebConfig.java | 6 ++ .../app/controller/filter/JwtFilter.java | 56 +++++++++++++++++++ .../java/com/health/app/util/JwtUtil.java | 32 +++++++++++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/health/app/config/SecurityConfig.java create mode 100644 src/main/java/com/health/app/controller/filter/JwtFilter.java create mode 100644 src/main/java/com/health/app/util/JwtUtil.java diff --git a/pom.xml b/pom.xml index ee558f6..99a45db 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,37 @@ 1.18.28 provided - + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.1 + + + + + org.springframework.boot + spring-boot-starter-security + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + diff --git a/src/main/java/com/health/app/AppApplication.java b/src/main/java/com/health/app/AppApplication.java index 742d339..213c5b2 100644 --- a/src/main/java/com/health/app/AppApplication.java +++ b/src/main/java/com/health/app/AppApplication.java @@ -1,5 +1,6 @@ package com.health.app; +import com.health.app.util.JwtUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -7,6 +8,7 @@ public class AppApplication { public static void main(String[] args) { + System.out.println( new JwtUtil().generateToken("admin")); SpringApplication.run(AppApplication.class, args); } diff --git a/src/main/java/com/health/app/config/SecurityConfig.java b/src/main/java/com/health/app/config/SecurityConfig.java new file mode 100644 index 0000000..f3340cc --- /dev/null +++ b/src/main/java/com/health/app/config/SecurityConfig.java @@ -0,0 +1,27 @@ +package com.health.app.config; + +import com.health.app.controller.filter.JwtFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // Desativar CSRF + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/public/**").permitAll() // Endpoints públicos + .anyRequest().authenticated() // Todos os outros endpoints são protegidos + ) + .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/health/app/config/WebConfig.java b/src/main/java/com/health/app/config/WebConfig.java index 45ab3fa..1c4b2fd 100644 --- a/src/main/java/com/health/app/config/WebConfig.java +++ b/src/main/java/com/health/app/config/WebConfig.java @@ -1,6 +1,7 @@ package com.health.app.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.health.app.controller.filter.JwtFilter; import com.health.app.integrations.openai.OpenAiClient; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Value; @@ -19,6 +20,11 @@ public class WebConfig { @Value("${integrations.openai.api-key}") private String openAiApiKey; + @Bean + public JwtFilter jwtFilter() { + return new JwtFilter(); + } + @Bean WebClient webClient(ObjectMapper objectMapper) { return WebClient.builder() diff --git a/src/main/java/com/health/app/controller/filter/JwtFilter.java b/src/main/java/com/health/app/controller/filter/JwtFilter.java new file mode 100644 index 0000000..38e297b --- /dev/null +++ b/src/main/java/com/health/app/controller/filter/JwtFilter.java @@ -0,0 +1,56 @@ +package com.health.app.controller.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.web.filter.GenericFilterBean; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; + +import java.io.IOException; + +public class JwtFilter extends GenericFilterBean { + + private String secret = "secreto"; // Sua chave secreta + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String authHeader = httpRequest.getHeader("Authorization"); + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token JWT ausente ou malformado"); + return; + } + + String token = authHeader.substring(7); // Remova o prefixo "Bearer " + + try { + Claims claims = Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + httpRequest.setAttribute("claims", claims); + } catch (Exception e) { + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token JWT inválido ou expirado"); + return; + } + + filterChain.doFilter(request, response); + } + + + @Override + public void destroy() { + } +} \ No newline at end of file diff --git a/src/main/java/com/health/app/util/JwtUtil.java b/src/main/java/com/health/app/util/JwtUtil.java new file mode 100644 index 0000000..1a5cca4 --- /dev/null +++ b/src/main/java/com/health/app/util/JwtUtil.java @@ -0,0 +1,32 @@ +package com.health.app.util; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; + + +public class JwtUtil { + private String secret = "secreto"; + + public String generateToken(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public Boolean validateToken(String token, String username) { + final String extractedUsername = extractUsername(token); + return (extractedUsername.equals(username) && !isTokenExpired(token)); + } + + private String extractUsername(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); + } + + private Boolean isTokenExpired(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getExpiration().before(new Date()); + } +} \ No newline at end of file From 3aeba0df67dd40719433f5eca9759c644235a3de Mon Sep 17 00:00:00 2001 From: Joao Pedro Truchinski Borba Date: Sun, 16 Jun 2024 13:16:49 -0300 Subject: [PATCH 2/4] feat: jwt auth --- .../com/health/app/config/SecurityConfig.java | 3 ++- .../health/app/controller/filter/JwtFilter.java | 17 +++++++++-------- src/main/java/com/health/app/util/JwtUtil.java | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/health/app/config/SecurityConfig.java b/src/main/java/com/health/app/config/SecurityConfig.java index f3340cc..a23d451 100644 --- a/src/main/java/com/health/app/config/SecurityConfig.java +++ b/src/main/java/com/health/app/config/SecurityConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; 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.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -15,7 +16,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()) // Desativar CSRF + .csrf(AbstractHttpConfigurer::disable) // Desativar CSRF .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() // Endpoints públicos .anyRequest().authenticated() // Todos os outros endpoints são protegidos diff --git a/src/main/java/com/health/app/controller/filter/JwtFilter.java b/src/main/java/com/health/app/controller/filter/JwtFilter.java index 38e297b..db4275a 100644 --- a/src/main/java/com/health/app/controller/filter/JwtFilter.java +++ b/src/main/java/com/health/app/controller/filter/JwtFilter.java @@ -17,7 +17,8 @@ public class JwtFilter extends GenericFilterBean { - private String secret = "secreto"; // Sua chave secreta + private final String secret = "secreto"; // Sua chave secreta + private final String prefix = "Bearer "; // Prefixo do token JWT @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) @@ -28,19 +29,19 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String authHeader = httpRequest.getHeader("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { + if (authHeader == null || !authHeader.startsWith(prefix)) { httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token JWT ausente ou malformado"); return; } - String token = authHeader.substring(7); // Remova o prefixo "Bearer " + String token = authHeader.substring(prefix.length()); // Remova o prefixo "Bearer " try { - Claims claims = Jwts.parser() - .setSigningKey(secret) - .parseClaimsJws(token) - .getBody(); - httpRequest.setAttribute("claims", claims); +// Claims claims = Jwts.parser() +// .setSigningKey(secret) +// .parseClaimsJws(token) +// .getBody(); +// httpRequest.setAttribute("claims", claims); } catch (Exception e) { httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token JWT inválido ou expirado"); return; diff --git a/src/main/java/com/health/app/util/JwtUtil.java b/src/main/java/com/health/app/util/JwtUtil.java index 1a5cca4..3e18c3a 100644 --- a/src/main/java/com/health/app/util/JwtUtil.java +++ b/src/main/java/com/health/app/util/JwtUtil.java @@ -11,8 +11,8 @@ public class JwtUtil { public String generateToken(String username) { return Jwts.builder() .setSubject(username) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 dia de expiração .signWith(SignatureAlgorithm.HS256, secret) .compact(); } From 1136910ba666159a47f26ab94f952efc78289ecb Mon Sep 17 00:00:00 2001 From: Joao Pedro Truchinski Borba Date: Sun, 16 Jun 2024 13:46:33 -0300 Subject: [PATCH 3/4] fix: request body --- src/main/java/com/health/app/controller/UserController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/health/app/controller/UserController.java b/src/main/java/com/health/app/controller/UserController.java index 9d9ec3d..8221039 100644 --- a/src/main/java/com/health/app/controller/UserController.java +++ b/src/main/java/com/health/app/controller/UserController.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @@ -14,7 +15,7 @@ public class UserController { private final UserService userService; @PostMapping("/user") - public ResponseEntity save(CreateUserRequestDTO requestDTO) { + public ResponseEntity save(@RequestBody CreateUserRequestDTO requestDTO) { userService.save(requestDTO); return ResponseEntity.ok("User created"); } From ddf3e23e7b2cc58bcdf55d7acf4ddb2850f4a60f Mon Sep 17 00:00:00 2001 From: Joao Pedro Truchinski Borba Date: Sun, 16 Jun 2024 14:13:12 -0300 Subject: [PATCH 4/4] feat: jwt testing --- .../com/health/app/config/SecurityConfig.java | 7 ++-- .../java/com/health/app/config/WebConfig.java | 5 --- .../app/controller/filter/JwtFilter.java | 34 ++++++++++++++++--- .../health/app/repository/UserRepository.java | 2 ++ .../com/health/app/services/UserService.java | 3 +- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/health/app/config/SecurityConfig.java b/src/main/java/com/health/app/config/SecurityConfig.java index a23d451..0816e83 100644 --- a/src/main/java/com/health/app/config/SecurityConfig.java +++ b/src/main/java/com/health/app/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.health.app.config; import com.health.app.controller.filter.JwtFilter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -11,17 +12,19 @@ @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final JwtFilter jwtFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) // Desativar CSRF .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/public/**").permitAll() // Endpoints públicos + .requestMatchers("/user").permitAll() // Endpoints públicos .anyRequest().authenticated() // Todos os outros endpoints são protegidos ) - .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/health/app/config/WebConfig.java b/src/main/java/com/health/app/config/WebConfig.java index 1c4b2fd..afcecc7 100644 --- a/src/main/java/com/health/app/config/WebConfig.java +++ b/src/main/java/com/health/app/config/WebConfig.java @@ -20,11 +20,6 @@ public class WebConfig { @Value("${integrations.openai.api-key}") private String openAiApiKey; - @Bean - public JwtFilter jwtFilter() { - return new JwtFilter(); - } - @Bean WebClient webClient(ObjectMapper objectMapper) { return WebClient.builder() diff --git a/src/main/java/com/health/app/controller/filter/JwtFilter.java b/src/main/java/com/health/app/controller/filter/JwtFilter.java index db4275a..7014895 100644 --- a/src/main/java/com/health/app/controller/filter/JwtFilter.java +++ b/src/main/java/com/health/app/controller/filter/JwtFilter.java @@ -1,5 +1,7 @@ package com.health.app.controller.filter; +import com.health.app.entity.User; +import com.health.app.repository.UserRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; @@ -8,6 +10,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import io.jsonwebtoken.Claims; @@ -15,10 +22,13 @@ import java.io.IOException; +@Component +@RequiredArgsConstructor public class JwtFilter extends GenericFilterBean { private final String secret = "secreto"; // Sua chave secreta private final String prefix = "Bearer "; // Prefixo do token JWT + private final UserRepository userRepository; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) @@ -27,6 +37,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; + if (((HttpServletRequest) request).getRequestURI().equals("/user")) { + filterChain.doFilter(request, response); + return; + } + String authHeader = httpRequest.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith(prefix)) { @@ -37,11 +52,20 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String token = authHeader.substring(prefix.length()); // Remova o prefixo "Bearer " try { -// Claims claims = Jwts.parser() -// .setSigningKey(secret) -// .parseClaimsJws(token) -// .getBody(); -// httpRequest.setAttribute("claims", claims); + Claims claims = Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + httpRequest.setAttribute("claims", claims); + + String username = claims.getSubject(); + User user = userRepository.findByEmail(username); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest)); + SecurityContextHolder.getContext().setAuthentication(authToken); + + + } catch (Exception e) { httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token JWT inválido ou expirado"); return; diff --git a/src/main/java/com/health/app/repository/UserRepository.java b/src/main/java/com/health/app/repository/UserRepository.java index 6235399..533d53f 100644 --- a/src/main/java/com/health/app/repository/UserRepository.java +++ b/src/main/java/com/health/app/repository/UserRepository.java @@ -6,4 +6,6 @@ public interface UserRepository extends JpaRepository { User save(User user); + + User findByEmail(String email); } diff --git a/src/main/java/com/health/app/services/UserService.java b/src/main/java/com/health/app/services/UserService.java index c10e854..2a9f30e 100644 --- a/src/main/java/com/health/app/services/UserService.java +++ b/src/main/java/com/health/app/services/UserService.java @@ -3,6 +3,7 @@ import com.health.app.dto.CreateUserRequestDTO; import com.health.app.entity.User; import com.health.app.repository.UserRepository; +import com.health.app.util.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,7 +20,7 @@ public User save(CreateUserRequestDTO createUserRequestDTO) { user.setName(createUserRequestDTO.getName()); user.setEmail(createUserRequestDTO.getEmail()); user.setPassword(createUserRequestDTO.getPassword()); - + System.out.println(new JwtUtil().generateToken(user.getEmail())); userRepository.save(user); return user; }