Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

11
respostas

Token inválido

Toda vez que a aplicação reinicia, o token é inválidado, tem alguma forma que eu possa alterar isso?

11 respostas

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.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

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:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

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.