25
respostas

Não consigo gerar Novo Token no login e as requisições não podem implementadas por "token Expirado"

Boa Noite!!

Após implementar a autenticação do token, não consigo gerar novo token (na requisição de login) (aparece a mensagem: ""message": "Token JWT não enviado no cabeçalho",

e devido o token estar Expirado as demais requisições apresenta a mensagem: "message": "TOKEN JWT INVÁLIDO OU EXPIRADO",

seguem os códigos das classes envolvidas:

CLASSE SecurityFilter:

@Component  // é algo genérico
public class SecurityFilter extends OncePerRequestFilter {

    //chama a classe tokenService para analizar o token
    @Autowired
    private TokenService tokenService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //o frontend irá enviar o token pelo cabelho autorization
        // APÓS REALIZAR O FILTER, ELE CHAMA: OU O PROXIMO FILTRO OO A CADEIA DE REQUISIÇÃO
        var tokenJWT = recuperarToken(request);
            var subject = tokenService.getSubject( tokenJWT );
            System.out.println( subject ); //imprime o nome do login
            System.out.println("chamou o fiter");
            System.out.println(tokenJWT); //imprime o token no console

        // vai ser encaminhado para o próximo filter o request e o response
        filterChain.doFilter( request, response );
    }
    private String recuperarToken(HttpServletRequest request) {
        // vai pegar o cabeçalho da requisição (onde virá o token)
        var authorizationHeader = request.getHeader( "Authorization" );
        // se não existir o cabeçalho vai lançar uma exception
        if (authorizationHeader == null){
            throw new RuntimeException("Token JWT não enviado no cabeçalho");
        }
        // para não vir no nome do cabeçalho(Bearer) foi feito um replace vazio
        return authorizationHeader.replace( "Bearer ", " " );
    }
}

CLASSE: TokenService:

@Service
public class TokenService {


    // aqu ficará a senha para gerar tokens
    @Value( "${api.security.token.secret}" ) // aqui indica para buscar a variável no application.properties
    private String secret;

    public String gerarToken(UsuarioEntidade usuarioEntidade){

        System.out.println(secret);

        try {

            // dentro do parentese vai uma senha para gerar tokens
            var algoritimo = Algorithm.HMAC256( secret);
            return JWT.create()
                    .withIssuer("API VOLL.med")
                    .withSubject( usuarioEntidade.getLogin() )
                    .withExpiresAt( dataExpiracao() )
                    .sign(algoritimo);
        } catch (JWTCreationException exception){
            throw new RuntimeException("erro ao gerar token", exception);
        }

    }

    ////++++++++++++++++++++TESTA SE O TOKEN RECEBIDO (SecurityFilter) ESTÁ VÁLIDO+++++++++++


    // recebe o token vindo da classe SecutityFilter
    public String getSubject(String tokenJWT){
        // vai verificar se o token é valido:

        try {
            var algoritimo = Algorithm.HMAC256( secret);
            return JWT.require(algoritimo)
                    .withIssuer("API VOLL.med")
                    .build()
                    .verify(tokenJWT)//verifica se o token está valido
                    .getSubject(); // pega o nome do login do token

        } catch (JWTVerificationException exception){
            exception.printStackTrace();
            throw new RuntimeException("TOKEN JWT INVÁLIDO OU EXPIRADO");

        }

    }


        // aqui ajusta o tempo de expiração do token ( no caso ficou 2 horas)
    private Instant dataExpiracao() {
        ZoneOffset zoneOffset = ZoneOffset.of("Z"); // Criando um ZoneOffset UTC
        return LocalDateTime.now().plusHours(2).atOffset(zoneOffset).toInstant();
    }

}

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

25 respostas

Oi!

Na sua classe SecurityFilter faltou o if para checar se está vindo o cabeçalho com o token:

var tokenJWT = recuperarToken(request);
if (tokenJWT != null) {
    var subject = tokenService.getSubject( tokenJWT);
    System.out.println( subject ); //imprime o nome do login
    System.out.println("chamou o fiter");
    System.out.println(tokenJWT); //imprime o token no console
}

Esse if é necessário, pois a requisição de login não envia o token no cabeçalho.

Bons estudos!

Rodrigo:

Muito obrigado pelo suporte (sempre recorrente)!!! Sem resolver este bug, não consigo prosseguir no curso, lamento insistir no pedido de ajuda.

Eu coloquei o If que você sugeriu:


porém, ao usar a requisição de login, continua criticando a ausencia do token no corpo da requisição: Insira aqui a descrição dessa imagem para ajudar na acessibilidadeAo tentar executar o login, está sendo exibido no console: java.lang.RuntimeException: Token JWT não enviado no cabeçalho

seguem os códigos das classes de autenticação:

CLASSE SecurityFilter:

@Component  // é algo genérico
public class SecurityFilter extends OncePerRequestFilter {

    //chama a classe tokenService para analizar o token
    @Autowired
    private TokenService tokenService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //o frontend irá enviar o token pelo cabelho autorization
        // APÓS REALIZAR O FILTER, ELE CHAMA: OU O PROXIMO FILTRO OO A CADEIA DE REQUISIÇÃO
        var tokenJWT = recuperarToken(request);
            if (tokenJWT != null) {
                var subject = tokenService.getSubject( tokenJWT );
                System.out.println( subject ); //imprime o nome do login
                System.out.println( "chamou o fiter" );
                System.out.println( tokenJWT ); //imprime o token no console
            }
        // vai ser encaminhado para o próximo filter o request e o response
        filterChain.doFilter( request, response );
    }
    private String recuperarToken(HttpServletRequest request) {
        // vai pegar o cabeçalho da requisição (onde virá o token)
        var authorizationHeader = request.getHeader( "Authorization" );
        // se não existir o cabeçalho vai lançar uma exception
        if (authorizationHeader == null){
            throw new RuntimeException("Token JWT não enviado no cabeçalho");
        }
        // para não vir no nome do cabeçalho(Bearer) foi feito um replace vazio
        return authorizationHeader.replace( "Bearer ", " " );
    }
}

CLASSE TokenService:

@Service
public class TokenService {


    // aqu ficará a senha para gerar tokens
    @Value( "${api.security.token.secret}" ) // aqui indica para buscar a variável no application.properties
    private String secret;

    public String gerarToken(UsuarioEntidade usuarioEntidade){

        System.out.println(secret);

        try {

            // dentro do parentese vai uma senha para gerar tokens
            var algoritimo = Algorithm.HMAC256( secret);
            return JWT.create()
                    .withIssuer("API VOLL.med")
                    .withSubject( usuarioEntidade.getLogin() )
                    .withExpiresAt( dataExpiracao() )
                    .sign(algoritimo);
        } catch (JWTCreationException exception){
            throw new RuntimeException("erro ao gerar token", exception);
        }

    }



   
    public String getSubject(String tokenJWT){
        // vai verificar se o token é valido:

        try {
            var algoritimo = Algorithm.HMAC256( secret);
            return JWT.require(algoritimo)
                    .withIssuer("API VOLL.med")
                    .build()
                    .verify(tokenJWT)//verifica se o token está valido
                    .getSubject(); // pega o nome do login do token

        } catch (JWTVerificationException exception){
            exception.printStackTrace();
            throw new RuntimeException("TOKEN JWT INVÁLIDO OU EXPIRADO");

        }

    }


        // aqui ajusta o tempo de expiração do token ( no caso ficou 2 horas)
    private Instant dataExpiracao() {
        ZoneOffset zoneOffset = ZoneOffset.of("Z"); // Criando um ZoneOffset UTC
        return LocalDateTime.now().plusHours(2).atOffset(zoneOffset).toInstant();
    }

}

CLASSE SecurityConfigurations:

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        return http.csrf().disable() //desabilita proteção redundante contra csrf
                .sessionManagement()
                .sessionCreationPolicy( SessionCreationPolicy.STATELESS ) // para não abrir o menu de login e senha
                .and().build();
    }

    @Bean  // serve para exportar uma classe para o Spring e fazer a injeção
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception{

        return configuration.getAuthenticationManager();
    }

    @Bean  // avisa que irá usar a criptografia (BCryptPassword)
    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder();
    }

Agora o problema é no seu método recuperarToken:

private String recuperarToken(HttpServletRequest request) {
    // vai pegar o cabeçalho da requisição (onde virá o token)
    var authorizationHeader = request.getHeader( "Authorization" );
    // se não existir o cabeçalho vai lançar uma exception
    if (authorizationHeader == null){
        throw new RuntimeException("Token JWT não enviado no cabeçalho");
    }
    // para não vir no nome do cabeçalho(Bearer) foi feito um replace vazio
    return authorizationHeader.replace( "Bearer ", " " );
}

Ele está implementando de um jeito que torna obrigatório o envio do token. Altere para:

private String recuperarToken(HttpServletRequest request) {
    var authorizationHeader = request.getHeader( "Authorization" );
    
    if (authorizationHeader != null) {
        //Aqui tinha um espaço em branco no segundo parametro do metodo replace (deve ser uma string vazia mesmo, sem espacos):
        return authorizationHeader.replace("Bearer ", "");
    }
    
    return null;
}

Outra coisa, no seu print do insmonia, na requisicao de listar os medicos, você está enviando o token com aspas duplas. Envie apenas o token sem as aspas.

Após executar as mudanças que vc sugeriu, o login voltou a funcionar ( agora está gerando um novo token);

Porém ao copiar este token e enviar na requisição de GET Listagem de médicos, foi retornado: "Token inválido ou Expirado", obs: eu retirei as aspas e enviei o token sem aspas

obs: Fiz um outro teste: se eu envio a requisição SEM o token, ela da 200ok, se eu pego o token recem gerado pelo login, dá a mensagem: "token inválido"

Manda aqui a stack trace que sai agora ao dar o erro de toklen invalido

Segue a stack trace ao dar token inválido: ( vou ter de enviar umas 4 vezes, pois estoura o limite de 5000 caracteres):

com.auth0.jwt.exceptions.JWTDecodeException: The input is not a valid base 64 encoded string. at com.auth0.jwt.JWTDecoder.(JWTDecoder.java:46) at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:444) at med.voll.api.infra.exeption.security.TokenService.getSubject(TokenService.java:57) at med.voll.api.infra.exeption.security.SecurityFilter.doFilterInternal(SecurityFilter.java:25) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)

mais 5.000 caracteres:

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalArgumentException: Illegal base64 character 20 at java.base/java.util.Base64$Decoder.decode0(Base64.java:852) at java.base/java.util.Base64$Decoder.decode(Base64.java:570) at java.base/java.util.Base64$Decoder.decode(Base64.java:593) at com.auth0.jwt.JWTDecoder.(JWTDecoder.java:41) ... 70 more 2023-11-09T13:53:54.005-03:00 ERROR 16964 --- [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.exeption.security.TokenService.getSubject(TokenService.java:62) ~[classes/:na] at med.voll.api.infra.exeption.security.SecurityFilter.doFilterInternal(SecurityFilter.java:25) ~[classes/:na] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15] at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) ~[spring-security-web-6.0.8.jar:6.0.8] at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) ~[spring-security-web-6.0.8.jar:6.0.8]

Pela mensagem inicial parece que o token está invalido mesmo.

Coloca um system.out para imprimir o token que está chegando:

public String getSubject(String tokenJWT) {
    System.out.println("TOKEN RECEBIDO: " +tokenJWT);

    // vai verificar se o token é valido:

    try {
        var algoritimo = Algorithm.HMAC256( secret);
        return JWT.require(algoritimo)
                .withIssuer("API VOLL.med")
                .build()
                .verify(tokenJWT)//verifica se o token está valido
                .getSubject(); // pega o nome do login do token

    } catch (JWTVerificationException exception){
        exception.printStackTrace();
        throw new RuntimeException("TOKEN JWT INVÁLIDO OU EXPIRADO");

    }

}

mais 5.000 caracteres:

2023-11-09T13:53:54.005-03:00 ERROR 16964 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.8.jar:6.0.8]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.0.8.jar:6.0.8]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.0.8.jar:6.0.8]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.13.jar:6.0.13]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.13.jar:6.0.13]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
TOKEN RECEBIDO:  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJldWRlcm1lczExQGdtYWlsLmNvbSIsImlzcyI6IkFQSSBWT0xMLm1lZCIsImV4cCI6MTY5OTU0MDIxNX0.KAnq-zFwc4kd3erxn4pPtxpfQPldx8671cHTEh1rGEA
2023-11-09T14:05:21.577-03:00 ERROR 6276 --- [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

To achando que tem um espaço em branco no iníco ou fim do seu token. Coloca um trim() no método:

private String recuperarToken(HttpServletRequest request) {
    var authorizationHeader = request.getHeader( "Authorization" );
    
    if (authorizationHeader != null) {
        return authorizationHeader.replace("Bearer ", "").trim();
    }
    
    return null;
}
return authorizationHeader.replace("Bearer ", "").trim();

O trim foi adicionado, mas o problema continua: "token inválido"

O problema é que o token enviado está expirado.

Pode ser na sua geração do token, que estão ficando com outro fuso horário:

private Instant dataExpiracao() {
    ZoneOffset zoneOffset = ZoneOffset.of("Z"); // Criando um ZoneOffset UTC
    return LocalDateTime.now().plusHours(2).atOffset(zoneOffset).toInstant();
}

Altere para:

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

Faça login novamente para pegar um novo token e teste a requisição de listar médicos com esse novo token.

Ao elaborar o código sugerido, passou a apresentar: 403 Forbidden

O código 403 Forbiden apresenta agora até para o request Login, (não consigo gerar novo token)

Suas configurações de segurança estão incompletas. Deveria estar assim:


@Autowired
private SecurityFilter securityFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().authorizeHttpRequests()
            .requestMatchers(HttpMethod.POST, "/login").permitAll()
            .anyRequest().authenticated()
            .and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

ao entar implementar a linha abaixo, o código quebra em cima da palavra: "securityFilter"

.and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)

tem que injetar como um novo atributo:

@Autowired
private SecurityFilter securityFilter;

Já tentei colocar o "S" maiusculo e continua quebrando

Aqui tem o repositório com o código do projeto: https://github.com/alura-cursos/2770-spring-boot

Melhor você avaliar diretamente as classes para ver o que está faltando no seu projeto, pois ele está com algumas diferenças.

após implementar o

@Autowired private SecurityFilter securityFilter;

Ele permite um novo token ser criado no login,

porem para as outras requisições fica com o 403 Forbidden

(com e sem o token habilitado, a mensagem é a mesma)

obs:

ao baixar o código completo ele não roda em minha maquina.

Em outra questão do forum , vc me disse que era porque meu java era versão 21, eu lhe enviei um print, mostrando que meu java é versão 17.

Depois não houve mais instruçoes

Estou com um sério problema para continuar o curso:

O meu código está bugado e quando baixo a sua versão completa do projeto, ela não roda em minha maquina,

Fiquei o dia todo hoje tentando resolver para continuar o curso e não consegui

As vezes o erro pode ser outro, mas o Spring Security interceta e devolve erro 403.

Deixa sua classe de tratamento de erros assim, para tratar as outras possibilidades:

@RestControllerAdvice
public class TratadorDeErros {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity tratarErro404() {
        return ResponseEntity.notFound().build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity tratarErro400(MethodArgumentNotValidException ex) {
        var erros = ex.getFieldErrors();
        return ResponseEntity.badRequest().body(erros.stream().map(DadosErroValidacao::new).toList());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity tratarErro400(HttpMessageNotReadableException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }

    @ExceptionHandler(ValidacaoException.class)
    public ResponseEntity tratarErroRegraDeNegocio(ValidacaoException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }

    @ExceptionHandler(BadCredentialsException.class)
    public ResponseEntity tratarErroBadCredentials() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Credenciais inválidas");
    }

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity tratarErroAuthentication() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Falha na autenticação");
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity tratarErroAcessoNegado() {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Acesso negado");
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity tratarErro500(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erro: " +ex.getLocalizedMessage());
    }

    private record DadosErroValidacao(String campo, String mensagem) {
        public DadosErroValidacao(FieldError erro) {
            this(erro.getField(), erro.getDefaultMessage());
        }
    }
}

E veja se muda o retorno no Insmonia