Toda vez que a aplicação reinicia, o token é inválidado, tem alguma forma que eu possa alterar isso?
Toda vez que a aplicação reinicia, o token é inválidado, tem alguma forma que eu possa alterar isso?
Oi Victor,
Provável que seja algum problema de configuração no seu projeto, pois no restart do servidor o token deveria continuar válido. Somente expira após o tempo configurado na criação do token. Verifica sua classe que cria o token se o tempo de expiração está sendo passado corretamente.
A princípio tudo ok.
Mas precisaria ver como você está executando a aplicação e disparando a requisição para obter o token.
No projeto do curso, ao fazer login você recebe um token e pode reiniciar a aplicação sem problemas, pois o mesmo token continua válido para as próximas requisções.
Acho que isso começou quando eu coloquei o @AuthenticationPrincipal
Testei aqui colocando isso no método de cadastrar tópico e funcionou normal também:
estou usando UUID, será que é por isso? toda vez que a aplicação reinicia, o ID do usuário troca.
Pior que não, pois o jjwt não checa as informações do token, apenas se ele é válido.
Posta aqui suas classes TokenService
, AutenticacaoViaTokenFilter
e SecurityConfigurations
package application.service.serviceAction;
import application.dto.response.UserMonitoringDTO;
import application.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service // Indica que é uma camada de serviço , o spring vai gerenciar automaticamente.
public class TokenServiceImpl implements TokenService { // Serviço relacionado ao token.
@Value("${jwt.expiration}") // var de ambiente , localização externa - application(test,prod).properties
private String expiration; // tempo de expiração do token
@Value("${jwt.secret}") // var de ambiente , localização externa - application(test,prod).properties
private String secret; // regra de como o token será codificado.
@Override
public String generateToken(Authentication authentication) { // Método que gera um token.
User logged = (User) authentication.getPrincipal();
Date today = new Date();
Date dateExpiration = new Date(today.getTime() + Long.parseLong(expiration));
UserMonitoringDTO userDTO = new UserMonitoringDTO(logged);
return Jwts.builder() // -> método cria o hash (token) inteiro com dados do usuario, segurança e alghoritmo de codificação e compacta.
.setSubject(userDTO.getId())
.claim("username", userDTO.getNickname())
.claim("email", userDTO.getEmail())
.claim("roles", userDTO.getRolesDTO())
.setIssuedAt(today)
.setExpiration(dateExpiration)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
@Override
public boolean isTokenValid(String token) { // Método que verifica se o Token é válido.
try {
Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public String getIdUser(String token) { // Método que recupera o Id do Usuário pelo token.
Claims claims = Jwts.parser().setSigningKey(this.secret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
package application.filter;
import application.entity.User;
import application.repository.UserRepository;
import application.service.exception.DatabaseException;
import application.service.serviceAction.TokenServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RequiredArgsConstructor
public class AuthenticationJWTFilter extends OncePerRequestFilter {
// Quando a aplicação subir, essa classe vai ser chamada antes do spring security.
// Nas proximas requisições , o spring security não vai ser chamado mais, pois as informações já serão salvas em memória,
// porém, esse filtro sera chamado em todas as requisições para recuperar o token, validar se o token está válido...
private final TokenServiceImpl tokenService;
private final UserRepository userRepository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) // Método para prosseguir com a requisição!
throws ServletException, IOException {
String token = recoverToken(request);
boolean valid = tokenService.isTokenValid(token); // retorna V/F se o token está valido
if (valid) { // se o token estiver válido
recoverUser(token); // autentica o usuário.
}
filterChain.doFilter(request, response);
}
private void recoverUser(String token) { // Método para recuperar o usuário e valida-lo através do token.
String idUser = tokenService.getIdUser(token);
User user = userRepository.findById(idUser).orElseThrow(
()->new DatabaseException("Error entering in the system, the token might be invalid"));
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private String recoverToken(HttpServletRequest request) { // Método para recuperar o token
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty() || !token.startsWith("Bearer ")) {
return null;
}
return token.substring(7);
}
}
package application.config.security;
import application.filter.AuthenticationJWTFilter;
import application.repository.UserRepository;
import application.service.serviceAction.TokenServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@RequiredArgsConstructor
// Faz com que quando a classe for instanciada, os atributos vão ser passados no construtor automaticamente.
@EnableWebSecurity
// Desabilita as configurações default do Spring Security, permitindo a gente á configurar as nossas próprias.
@Configuration // Indica que é uma classe de configuração
public class SecurityConfigurationsImpl implements SecurityConfigurations { // As classes de Security só são chamadas quando a aplicação sobe!
// Nas proximas requisições, essa classe não é chamada dnv, pois as conf já estão salvas.
private final TokenServiceImpl tokenService; // Classe que contém ações de um token como gerar um token...
private final UserRepository userRepository; // Repositório da entidade Usuário
@Override
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { // AuthManager, Conf de autenticação.
return authenticationConfiguration.getAuthenticationManager();
}
@Override
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // Configs de Autorização.
http.authorizeRequests()// Autorização de requests
.antMatchers("/auth").permitAll() // Estou permitindo TUDO E TODOS acessarem esse recurso no sistema.
.antMatchers("/userarea/**").authenticated() // Para acessar esse recurso, tem q estar autenticado.
.antMatchers("/users/register").permitAll() // Estou permitindo TUDO E TODOS acessarem esse recurso no sistema.
.antMatchers("/users/**").hasRole("ADMINISTRATOR") // Para acessas esse curso, a pessoa logada tem que ser ADM
.anyRequest().denyAll() // Qualquer outro recurso, sem ser os de cima, serão bloqueados ( securança pra manutenção). No caso, não tem outros recurso.
.and().exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint()) // Essa linha vai chamar a classe Unhautorize... para lídar com o erro 401.
.and().cors() // Libera a integração de aplicações externas como o front-end á essa API.
.and().headers().frameOptions().disable() // É para bloquear a página de login ser colocada em um iFrame
.and().csrf().disable() // Comentário sobre essa conf na linha 88.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(new AuthenticationJWTFilter(tokenService, userRepository), UsernamePasswordAuthenticationFilter.class); // Adiciona um FILTRO, Antes
// dessa classe ser chamada.
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers
("/swagger-ui/**", "/v3/api-docs/**", "/h2-console/**");
}
@Bean
public CorsConfigurationSource corsConfigurationSource() { // Método relacionado á CORS, integração com um meio externo.
CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "OPTIONS"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Acho que pode ser sim então essa questão do ID mudar a cada restart, por conta desse trecho no filter:
String idUser = tokenService.getIdUser(token);
User user = userRepository.findById(idUser).orElseThrow(()->new DatabaseException("Error entering in the system, the token might be invalid"));
Como mudou o id, ele não encontra o registro no banco e deve estar lançando essa exception.