Solucionado (ver solução)

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!

Solucionado
(ver solução)
10
respostas

[Dúvida] Token JWT inválido ou expirado!

Spring Boot 3: aplique boas práticas e proteja uma API Rest

Bom dia, Estou travado no projeto no pacote 05 aula 10 Testando o controle de acesso. Ao efetuar uma requisição pelo Insomnia, apresenta o erro abaixo na console.:

Chamando Filter Recebeu token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbmEuc291emFAdm9sbC5tZWQiLCJpc3MiOiJBUEkgdm9sbC5tZWQiLCJleHAiOjE3MTQwNDQ4MjJ9.W7eXFBeJcbCYcjk3Xl8JFaeH6BrlIYHyjTlVKebP3pc 2024-04-25T06:34:10.469-03:00 ERROR 2104 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.RuntimeException: Token JWT inválido ou expirado! at med.voll.api.infra.secutity.TokenService.getSubject(TokenService.java:43) ~[classes/:na] at med.voll.api.infra.secutity.SecurityFilter.doFilterInternal(SecurityFilter.java:32) ~[classes/:na] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]

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

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

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

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

Alguém pode me ajudar?

10 respostas

Oi!

Manda aqui o código completo da sua classe TokenService.

package med.voll.api.infra.secutity;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import med.voll.api.domain.usuario.Usuario;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Service
public class TokenService {

    @Value("${api.security.token.secret}")
    private String secret;
    public String gerarToken(Usuario usuario){

        try {
            var algoritimo = Algorithm.HMAC256(secret);
            return JWT.create()
                    .withIssuer("API voll.med")
                    .withSubject(usuario.getLogin())
                    .withExpiresAt(dataExpiracao())
                    .sign(algoritimo);
        }catch (JWTCreationException exception){
            throw new RuntimeException("Erro ao Gerar token jwt", exception);
        }
    }

    public String getSubject(String tokenJWT) {
        try {
            var algoritmo = Algorithm.HMAC256(secret);
            return JWT.require(algoritmo)
                    .withIssuer("API Voll.med")
                    .build()
                    .verify(tokenJWT)
                    .getSubject();
        } catch (JWTVerificationException exception) {
            throw new RuntimeException("Token JWT inválido ou expirado!");
        }
    }


    private Instant dataExpiracao() {

        return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
    }

}

O problema está no issuer.

No método gerarToken está como API voll.med, mas no método getSubject está como API Voll.med (letra V em maiúsculo).

Uma sugestão é declarar uma constante na classe para guardar essa informação e a utilizar nos dois métodos:

private static final String ISSUER = "API Voll.med";

Rodirgo fiz o ajuste mas ainda persiste o mesmo erro

    private static final String ISSUER = "API Voll.med";
    public String gerarToken(Usuario usuario){

        try {
            var algoritimo = Algorithm.HMAC256(secret);
            return JWT.create()
                    .withIssuer("API Voll.med")
                    .withSubject(usuario.getLogin())
                    .withExpiresAt(dataExpiracao())
                    .sign(algoritimo);
        }catch (JWTCreationException exception){
            throw new RuntimeException("Erro ao Gerar token jwt", exception);
        }
    }

    public String getSubject(String tokenJWT) {
        try {
            var algoritmo = Algorithm.HMAC256(secret);
            return JWT.require(algoritmo)
                    .withIssuer("API Voll.med")
                    .build()
                    .verify(tokenJWT)
                    .getSubject();
        } catch (JWTVerificationException exception) {
            throw new RuntimeException("Token JWT inválido ou expirado!");
        }
    }

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

Você precisa fazer login novamente para gerar um novo token, pois o anterior está vinculado com o issuer anterior.

Mesma erro.:

2024-04-25T09:51:23.747-03:00  INFO 16208 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
Chamando Filter
Recebeu token: null
Hibernate: 
    select
        u1_0.id,
        u1_0.login,
        u1_0.senha 
    from
        usuarios u1_0 
    where
        u1_0.login=?
Chamando Filter
Recebeu token:  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbmEuc291emFAdm9sbC5tZWQiLCJpc3MiOiJBUEkgVm9sbC5tZWQiLCJleHAiOjE3MTQwNTY2ODR9.Z7zqtd321gnWheHCcrlT9aW-nkswswuH9GVpaIkhmB0
2024-04-25T09:51:50.056-03:00 ERROR 16208 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.RuntimeException: Token JWT inválido ou expirado!
    at med.voll.api.infra.secutity.TokenService.getSubject(TokenService.java:45) ~[classes/:na]
    at med.voll.api.infra.secutity.SecurityFilter.doFilterInternal(SecurityFilter.java:32) ~[classes/:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]

Manda aqui então o código da sua classe SecurityFilter, que pode ter algum problema nela

package med.voll.api.infra.secutity;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import med.voll.api.domain.UsuarioRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class SecurityFilter extends OncePerRequestFilter {

    @Autowired
    private TokenService tokenService;
    @Autowired
    private UsuarioRepository repository;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        System.out.println("Chamando Filter");
        var tokenJWT = recuperarToken(request);

        System.out.println("Recebeu token: " +tokenJWT);

        if (tokenJWT != null){
            var subject = tokenService.getSubject(tokenJWT);
            var usuario = repository.findByLogin(subject);

            var authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            System.out.println("logado na requisição !!");
        }
        filterChain.doFilter(request, response);
    }

    private String recuperarToken(HttpServletRequest request) {
        var authorizationHeader = request.getHeader("Authorization");
        if (authorizationHeader != null) {
            //throw new RuntimeException("Token JWT não enviado no cabeçaçho Authorization");
            return  authorizationHeader.replace("Bearer", "");
        }

        return null;
    }
}
solução!

Nesse return:

return  authorizationHeader.replace("Bearer", "");

Faltou o espaço em branco após a palavra Bearer. Correto:

return  authorizationHeader.replace("Bearer ", "");

Top....... Valeu Rodrigo

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

Deu certo erra isso mesmo, não tinha espaço no "Bearer", depois que coloquei funcionou ...