Skip to content

Commit 16cde75

Browse files
change token do httpOnly
1 parent c004418 commit 16cde75

24 files changed

Lines changed: 421 additions & 108 deletions

src/main/java/com/virtusconsultoria/configsecurity/SecurityConfig.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ public SecurityFilterChain filtrarCadeiaDeSeguranca(HttpSecurity httpSecurity) t
7878
.requestMatchers(HttpMethod.GET, "/propostas/**").authenticated()
7979
.requestMatchers(HttpMethod.GET, "/clientes/**").authenticated()
8080

81+
// SecurityConfig.java
82+
.requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
83+
.requestMatchers(HttpMethod.POST, "/auth/refresh").permitAll()
84+
.requestMatchers(HttpMethod.GET, "/auth/me").authenticated()
85+
.requestMatchers(HttpMethod.POST, "/auth/logout").authenticated()
86+
8187
// Qualquer outra requisição precisa de autenticação
8288
.anyRequest().authenticated()
8389
)
@@ -110,16 +116,14 @@ public PasswordEncoder passwordEncoder() {
110116
public CorsConfigurationSource corsConfigurationSource() {
111117
CorsConfiguration configuration = new CorsConfiguration();
112118
configuration.setAllowedOrigins(Arrays.asList(
119+
"http://localhost:5173",
113120
"http://localhost:8081",
114121
"http://localhost:8080",
115-
"http://localhost:8080/",
116-
"http://localhost:8081/",
117-
"http://192.168.0.38:8080/",
118-
"https://front-end-virtus.vercel.app/"
122+
"https://front-end-virtus.vercel.app"
119123
));
120124
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
121125
configuration.setAllowedHeaders(Arrays.asList("*"));
122-
configuration.setAllowCredentials(true);
126+
configuration.setAllowCredentials(true); // CRUCIAL para cookies
123127

124128
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
125129
source.registerCorsConfiguration("/**", configuration);
Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,67 @@
1+
// src/main/java/com/virtusconsultoria/configsecurity/TokenService.java
12
package com.virtusconsultoria.configsecurity;
23

34
import com.auth0.jwt.JWT;
45
import com.auth0.jwt.algorithms.Algorithm;
56
import com.auth0.jwt.exceptions.JWTCreationException;
67
import com.auth0.jwt.exceptions.JWTVerificationException;
7-
import com.virtusconsultoria.model.Administrador;
8-
import com.virtusconsultoria.model.Colaborador;
8+
import com.virtusconsultoria.model.RefreshToken;
9+
import com.virtusconsultoria.repository.RefreshTokenRepository;
10+
import org.springframework.beans.factory.annotation.Autowired;
911
import org.springframework.beans.factory.annotation.Value;
1012
import org.springframework.security.core.userdetails.UserDetails;
1113
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
1215

1316
import java.time.Instant;
1417
import java.time.LocalDateTime;
1518
import java.time.ZoneOffset;
19+
import java.util.Optional;
20+
import java.util.UUID;
1621

1722
@Service
1823
public class TokenService {
1924

2025
@Value("${minha.chave.secreta}")
2126
private String palavraSecreta;
2227

23-
public String gerarToken(UserDetails userDetails){
24-
System.out.println("Gerando token para: " + userDetails.getUsername());
28+
@Autowired
29+
private RefreshTokenRepository refreshTokenRepository;
30+
31+
// Access Token: 15 minutos
32+
public String gerarAccessToken(UserDetails userDetails) {
2533
try {
2634
Algorithm algoritmo = Algorithm.HMAC256(palavraSecreta);
27-
String token = JWT.create()
35+
return JWT.create()
2836
.withIssuer("virtus-api")
2937
.withSubject(userDetails.getUsername())
30-
.withExpiresAt(gerarDataDeExpiracao())
38+
.withExpiresAt(gerarDataExpiracaoAccessToken())
3139
.sign(algoritmo);
32-
33-
return token;
3440
} catch (JWTCreationException erro) {
35-
return erro.getMessage();
41+
throw new RuntimeException("Erro ao gerar access token", erro);
3642
}
3743
}
3844

39-
public String validarToken(String token){
45+
// Refresh Token: 7 dias (armazenado no banco)
46+
@Transactional
47+
public String gerarRefreshToken(String userEmail) {
48+
// Revoga tokens anteriores do usuário
49+
refreshTokenRepository.revokeAllByUserEmail(userEmail);
50+
51+
// Cria novo refresh token
52+
RefreshToken refreshToken = RefreshToken.builder()
53+
.token(UUID.randomUUID().toString())
54+
.userEmail(userEmail)
55+
.expiryDate(Instant.now().plusSeconds(7 * 24 * 60 * 60)) // 7 dias
56+
.createdAt(Instant.now())
57+
.revoked(false)
58+
.build();
59+
60+
refreshTokenRepository.save(refreshToken);
61+
return refreshToken.getToken();
62+
}
63+
64+
public String validarAccessToken(String token) {
4065
try {
4166
Algorithm algoritmo = Algorithm.HMAC256(palavraSecreta);
4267
return JWT.require(algoritmo)
@@ -45,12 +70,30 @@ public String validarToken(String token){
4570
.verify(token)
4671
.getSubject();
4772
} catch (JWTVerificationException erro) {
48-
return erro.getMessage();
73+
return null;
4974
}
5075
}
5176

52-
public Instant gerarDataDeExpiracao(){
53-
return LocalDateTime.now().plusHours(16).toInstant(ZoneOffset.of("-03:00"));
77+
@Transactional
78+
public Optional<String> validarRefreshToken(String token) {
79+
return refreshTokenRepository.findByToken(token)
80+
.filter(rt -> !rt.isRevoked())
81+
.filter(rt -> rt.getExpiryDate().isAfter(Instant.now()))
82+
.map(RefreshToken::getUserEmail);
83+
}
84+
85+
@Transactional
86+
public void revogarRefreshToken(String token) {
87+
refreshTokenRepository.findByToken(token)
88+
.ifPresent(rt -> {
89+
rt.setRevoked(true);
90+
refreshTokenRepository.save(rt);
91+
});
5492
}
5593

56-
}
94+
private Instant gerarDataExpiracaoAccessToken() {
95+
return LocalDateTime.now()
96+
.plusMinutes(15) // 15 minutos
97+
.toInstant(ZoneOffset.of("-03:00"));
98+
}
99+
}
Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
// src/main/java/com/virtusconsultoria/configsecurity/VerificarToken.java
12
package com.virtusconsultoria.configsecurity;
23

34
import com.virtusconsultoria.service.AuthorizationService;
45
import jakarta.servlet.FilterChain;
56
import jakarta.servlet.ServletException;
7+
import jakarta.servlet.http.Cookie;
68
import jakarta.servlet.http.HttpServletRequest;
79
import jakarta.servlet.http.HttpServletResponse;
810
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -12,8 +14,8 @@
1214
import org.springframework.web.filter.OncePerRequestFilter;
1315

1416
import java.io.IOException;
17+
import java.util.Arrays;
1518

16-
//Anotação usada para falar ao string que ele deve gerenciar essa classe
1719
@Component
1820
public class VerificarToken extends OncePerRequestFilter {
1921

@@ -29,28 +31,34 @@ public VerificarToken(TokenService tokenService, AuthorizationService authorizat
2931
protected void doFilterInternal(
3032
HttpServletRequest request,
3133
HttpServletResponse response,
32-
FilterChain filterChain) throws ServletException, IOException
33-
{
34-
var token = this.recuperarToken(request);
35-
if(token != null){
36-
var login = tokenService.validarToken(token);
37-
if (!login.isEmpty()) {
38-
// Use o serviço unificado para carregar o usuário
34+
FilterChain filterChain) throws ServletException, IOException {
35+
36+
String token = recuperarTokenDoCookie(request);
37+
38+
if (token != null) {
39+
String login = tokenService.validarAccessToken(token);
40+
41+
if (login != null) {
3942
UserDetails user = authorizationService.loadUserByUsername(login);
4043
var authentication = new UsernamePasswordAuthenticationToken(
41-
user,
42-
null,
43-
user.getAuthorities());
44-
44+
user, null, user.getAuthorities()
45+
);
4546
SecurityContextHolder.getContext().setAuthentication(authentication);
4647
}
4748
}
49+
4850
filterChain.doFilter(request, response);
4951
}
5052

51-
private String recuperarToken(HttpServletRequest request){
52-
var authHeader = request.getHeader("Authorization");
53-
if(authHeader == null) return null;
54-
return authHeader.replace("Bearer ", "");
53+
private String recuperarTokenDoCookie(HttpServletRequest request) {
54+
Cookie[] cookies = request.getCookies();
55+
if (cookies != null) {
56+
return Arrays.stream(cookies)
57+
.filter(cookie -> "accessToken".equals(cookie.getName()))
58+
.map(Cookie::getValue)
59+
.findFirst()
60+
.orElse(null);
61+
}
62+
return null;
5563
}
5664
}
Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// src/main/java/com/virtusconsultoria/controllers/AuthenticationController.java
12
package com.virtusconsultoria.controllers;
23

34
import com.virtusconsultoria.configsecurity.TokenService;
@@ -9,6 +10,10 @@
910
import com.virtusconsultoria.model.Administrador;
1011
import com.virtusconsultoria.model.Colaborador;
1112
import com.virtusconsultoria.model.Supervisor;
13+
import com.virtusconsultoria.service.AuthorizationService;
14+
import jakarta.servlet.http.Cookie;
15+
import jakarta.servlet.http.HttpServletRequest;
16+
import jakarta.servlet.http.HttpServletResponse;
1217
import jakarta.validation.Valid;
1318
import org.springframework.beans.factory.annotation.Autowired;
1419
import org.springframework.http.HttpStatus;
@@ -19,6 +24,8 @@
1924
import org.springframework.security.core.userdetails.UserDetails;
2025
import org.springframework.web.bind.annotation.*;
2126

27+
import java.util.Map;
28+
2229
@RestController
2330
@RequestMapping("/auth")
2431
public class AuthenticationController {
@@ -29,30 +36,114 @@ public class AuthenticationController {
2936
@Autowired
3037
private TokenService tokenService;
3138

39+
@Autowired
40+
private AuthorizationService authorizationService;
41+
3242
@PostMapping("/login")
33-
@ResponseStatus(HttpStatus.CREATED)
34-
public ResponseEntity<LoginUserResponse> login(@RequestBody @Valid LoginRequestDto loginDto) {
35-
var usernamePassword = new UsernamePasswordAuthenticationToken(loginDto.email(), loginDto.senha());
36-
Authentication auth = this.authenticationManager.authenticate(usernamePassword);
43+
@ResponseStatus(HttpStatus.OK)
44+
public ResponseEntity<LoginUserResponse> login(
45+
@RequestBody @Valid LoginRequestDto loginDto,
46+
HttpServletResponse response
47+
) {
48+
var usernamePassword = new UsernamePasswordAuthenticationToken(
49+
loginDto.email(),
50+
loginDto.senha()
51+
);
52+
Authentication auth = authenticationManager.authenticate(usernamePassword);
53+
54+
UserDetails userDetails = (UserDetails) auth.getPrincipal();
55+
56+
// Gera tokens
57+
String accessToken = tokenService.gerarAccessToken(userDetails);
58+
String refreshToken = tokenService.gerarRefreshToken(userDetails.getUsername());
59+
60+
// Define cookies httpOnly
61+
Cookie accessCookie = criarCookie("accessToken", accessToken, 15 * 60); // 15 min
62+
Cookie refreshCookie = criarCookie("refreshToken", refreshToken, 7 * 24 * 60 * 60); // 7 dias
63+
64+
response.addCookie(accessCookie);
65+
response.addCookie(refreshCookie);
66+
67+
// Retorna dados públicos do usuário
68+
Object userResponseDto = montarUserResponse(auth.getPrincipal());
69+
70+
return ResponseEntity.ok(new LoginUserResponse(null, userResponseDto));
71+
}
72+
73+
@PostMapping("/refresh")
74+
public ResponseEntity<?> refresh(
75+
@CookieValue(name = "refreshToken", required = false) String refreshToken,
76+
HttpServletResponse response
77+
) {
78+
if (refreshToken == null) {
79+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
80+
.body("Refresh token não fornecido");
81+
}
82+
83+
return tokenService.validarRefreshToken(refreshToken)
84+
.map(email -> {
85+
UserDetails userDetails = authorizationService.loadUserByUsername(email);
86+
String newAccessToken = tokenService.gerarAccessToken(userDetails);
87+
88+
Cookie accessCookie = criarCookie("accessToken", newAccessToken, 15 * 60);
89+
response.addCookie(accessCookie);
3790

38-
System.out.println("UserNamePassword" + usernamePassword);
39-
40-
// Gera o token com o usuário autenticado (que pode ser de qualquer tipo)
41-
var token = tokenService.gerarToken((UserDetails) auth.getPrincipal());
91+
return ResponseEntity.ok().body("Token renovado com sucesso");
92+
})
93+
.orElseGet(() -> ResponseEntity.status(HttpStatus.UNAUTHORIZED)
94+
.body("Refresh token inválido ou expirado"));
95+
}
96+
97+
@PostMapping("/logout")
98+
public ResponseEntity<?> logout(
99+
@CookieValue(name = "refreshToken", required = false) String refreshToken,
100+
HttpServletResponse response
101+
) {
102+
// Revoga refresh token
103+
if (refreshToken != null) {
104+
tokenService.revogarRefreshToken(refreshToken);
105+
}
106+
107+
// Limpa cookies
108+
Cookie accessCookie = criarCookie("accessToken", "", 0);
109+
Cookie refreshCookie = criarCookie("refreshToken", "", 0);
110+
111+
response.addCookie(accessCookie);
112+
response.addCookie(refreshCookie);
113+
114+
return ResponseEntity.ok("Logout realizado com sucesso");
115+
}
42116

43-
// Pega o objeto principal (o usuário completo)
44-
Object principal = auth.getPrincipal();
45-
Object userResponseDto = null;
117+
@GetMapping("/me")
118+
public ResponseEntity<?> getCurrentUser(Authentication authentication) {
119+
if (authentication == null || !authentication.isAuthenticated()) {
120+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
121+
}
122+
123+
Object principal = authentication.getPrincipal();
124+
Object userResponseDto = montarUserResponse(principal);
125+
126+
return ResponseEntity.ok(Map.of("user", userResponseDto));
127+
}
46128

47-
// Verifica qual é o tipo do usuário para criar o DTO de resposta correto
129+
private Cookie criarCookie(String nome, String valor, int maxAge) {
130+
Cookie cookie = new Cookie(nome, valor);
131+
cookie.setHttpOnly(true);
132+
cookie.setSecure(true); // HTTPS apenas em produção
133+
cookie.setPath("/");
134+
cookie.setMaxAge(maxAge);
135+
cookie.setAttribute("SameSite", "Strict");
136+
return cookie;
137+
}
138+
139+
private Object montarUserResponse(Object principal) {
48140
if (principal instanceof Administrador user) {
49-
userResponseDto = new AdministradorResponseDto(user);
141+
return new AdministradorResponseDto(user);
50142
} else if (principal instanceof Supervisor user) {
51-
userResponseDto = new SupervisorResponseDto(user);
143+
return new SupervisorResponseDto(user);
52144
} else if (principal instanceof Colaborador user) {
53-
userResponseDto = new ColaboradorResponseDto(user);
145+
return new ColaboradorResponseDto(user);
54146
}
55-
56-
return ResponseEntity.ok(new LoginUserResponse(token, userResponseDto));
147+
return null;
57148
}
58149
}

src/main/java/com/virtusconsultoria/dtos/cliente/ClienteResponseDto.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
public record ClienteResponseDto (
1010
Long ID_CLIENTE,
11-
Colaborador colaborador,
11+
String nomeColaborador,
12+
Long ID_COLABORADOR,
1213
String nome,
1314
String cpf,
1415
String email,
@@ -39,7 +40,8 @@ public record ClienteResponseDto (
3940
public ClienteResponseDto(Clientes clientes){
4041
this(
4142
clientes.getID_CLIENTE(),
42-
clientes.getColaborador(),
43+
clientes.getColaborador().getNome(),
44+
clientes.getColaborador().getID_COLABORADOR(),
4345
clientes.getNome(),
4446
clientes.getCpf(),
4547
clientes.getEmail(),

0 commit comments

Comments
 (0)