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!

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