Solucionado (ver solução)
Solucionado
(ver solução)
23
respostas

[Bug] Erro na função getSubject

A função getSubject está indo sempre diretamente a exceção, coloquei para que printe o token para ver se realmente está inválido e não estavaInsira aqui a descrição dessa imagem para ajudar na acessibilidade Lembrando que defini esse ISSUER mais acima como "private static final String ISSUER = "API sevmark";" que é o issuer que eu ja estava usando no meu projeto, está basicamente tudo parecido com o demonstrado em aula, menos essa parte que eu defini outro nome

23 respostas

Oi!

Manda aqui o erro que saiu no console.

a exceção me retornou isto "Falha ao verificar o token JWT: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256"

Manda aqui o código completo da sua classe TokenService e o token que chegou na api e foi impresso no console

package com.sevmark.SevMark.services;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.sevmark.SevMark.model.usuario.Usuario;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Base64;

@Service
public class TokenService {

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

    private static final String ISSUER = "API sevmark";

    public String gerarToken(Usuario usuario) throws JWTVerificationException, UnsupportedEncodingException {
        try {
            var algoritmo = Algorithm.HMAC256(Base64.getDecoder().decode(secret));
            return JWT.create()
                    .withIssuer(ISSUER)
                    .withSubject(usuario.getLogin())
                    .withClaim("name", usuario.getUsername())
                    .withExpiresAt(dataExpiracao())
                    .sign(algoritmo);
        } catch (JWTCreationException exception){
            throw new RuntimeException("erro ao gerar token jwt", exception);
        }
    }

    public String getSubject(String tokenJWT) throws JWTVerificationException, UnsupportedEncodingException {
        try {
            var algoritmo = Algorithm.HMAC256(Base64.getDecoder().decode(secret));
            String subject = JWT.require(algoritmo)
                    .withIssuer(ISSUER)
                    .build()
                    .verify(tokenJWT)
                    .getSubject();
            System.out.println("Token JWT válido. Subject: " + subject);
            return subject;
        } catch (JWTVerificationException exception) {
            System.err.println("Falha ao verificar o token JWT: " + exception.getMessage());
            throw new RuntimeException("Token JWT inválido ou expirado: " + tokenJWT);
        }
    }

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

Token mostrado no console foi este Token JWT inválido ou expirado: eyJ0b2tlbkpXVCI6ImV5SjBiMnRsYmtwWFZDSTZJbVY1U2pCbFdFRnBUMmxLUzFZeFVXbE1RMHBvWWtkamFVOXBTa2xWZWtreFRtbEtPUzVsZVVwNlpGZEphVTlwU21oaWJsSjJZbTFzZGxrelNqRmxiV1IyWWxkV2VrOUVaM2RSUjJSMFdWZHNjMHh0VG5aaVUwbHpTVzFzZW1ONVNUWkphMFpSVTFOQ2VscFlXblJaV0VweVNXbDNhVnBZYUhkSmFtOTRUbnBGZVU1cVNUUk5WRVUwWmxFdWR6SmxiVTVhVlZWNVVrNWpXblZCWDAxdFJ6Wk5WRVI0TVdoNFJrNXhSMVp0TVdzNE1tMVFjMU5NZHlJc0ltRnNaeUk2SWtoVE1qVTJJbjAuZTMwLmV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSmhiblJ2Ym1sdlkzSjFlbWR2YldWek9EZ3dRR2R0WVdsc0xtTnZiU0lzSW1semN5STZJa0ZRU1NCelpYWnRZWEpySWl3aWJtRnRaU0k2SW1GdWRHOXVhVzlqY25WNloyOXRaWE00T0RCQVoyMWhhV3d1WTI5dElpd2laWGh3SWpveE56RXlOak13T0RZemZRLkNpajIwdXRQdDMyYmpFVlIxblBKN0pHRlUzN01pRi1HbEI4RzV3MnZVY016RjhQN0dQM01waDlrOXNTVFllRG5UT3RmNlNRZVpVaVluTmVHLVVyNURvIiwiYWxnIjoiSFMyNTYifQ.e30.HJsrgqRnQtrJb8ZIewa2FnpNPrF9htif9gn2j49guZ8

O problema é esse token que está chegando na API que está inválido.

Faça login novamente e gere um novo token para verificar se o token é na geração do token ou no envio do token para a API

mas não esta conseguindo realizar o login, ele gera o token e está me retornando no erro, e o erro está sendo retornado na função getSubject

Manda aqui as suas classes SecurityFilter e AutenticacaoController para eu verificar se tem algum problema no código da API

AutenticaçãoController

package com.sevmark.SevMark.controller;

import com.sevmark.SevMark.DTO.Authentication;
import com.sevmark.SevMark.DTO.TokenJWT;
import com.sevmark.SevMark.services.TokenService;
import com.sevmark.SevMark.model.usuario.Usuario;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/authentication")
public class AutenticationController {
    @Autowired
    private AuthenticationManager manager;

    @Autowired
    private TokenService tokenService;
    @PostMapping
    public ResponseEntity efetuarLogin(@RequestBody @Valid Authentication dados) {
        try {
            var authenticationToken = new UsernamePasswordAuthenticationToken(dados.login(), dados.password());
            var authentication = manager.authenticate(authenticationToken);

            var tokenJWT = tokenService.gerarToken((Usuario) authentication.getPrincipal());
            System.out.println(tokenJWT);
            return ResponseEntity.ok(new TokenJWT(tokenJWT));
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}

SecurityFilter

package com.sevmark.SevMark.config.security;

import com.sevmark.SevMark.repository.UsuarioRepository;
import com.sevmark.SevMark.services.TokenService;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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 {
        var tokenJWT = recuperarToken(request);
        System.out.println(tokenJWT);
        if (tokenJWT != null) {
            var subject = tokenService.getSubject(tokenJWT);
            var usuario = repository.findByLogin(subject);
            System.out.println(subject);
            var authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

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

        return null;
    }

}

O código a princípio está correto. No seu controller tem essas linhas:

var tokenJWT = tokenService.gerarToken((Usuario) authentication.getPrincipal());
System.out.println(tokenJWT);
return ResponseEntity.ok(new TokenJWT(tokenJWT));

Altera esse System.out para:

System.out.println("TOKEN GERADO NO LOGIN:" +tokenJWT);

Dispare uma requisição para efetuar login e mande aqui a saída no console desse System.out.

me retornou este

TOKEN GERADO NO LOGIN:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbnRvbmlvY3J1emdvbWVzODgwQGdtYWlsLmNvbSIsImlzcyI6IkFQSSBzZXZtYXJrIiwibmFtZSI6ImFudG9uaW9jcnV6Z29tZXM4ODBAZ21haWwuY29tIiwiZXhwIjoxNzEyNzg0NDc3fQ.aGw-K2CdGhcpi293radtx7sQnr1v2Io8Rn0pKrJUY2o

Esse token que foi gerado agora está válido. Tenta agora disparar a requisição que estava dando erro enviando esse novo token no header

Deu erro, e o estranho é que no erro está me retornando outro token, vou mandar o erro "message": "Token JWT inválido ou expirado: eyJ0b2tlbkpXVCI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSmhiblJ2Ym1sdlkzSjFlbWR2YldWek9EZ3dRR2R0WVdsc0xtTnZiU0lzSW1semN5STZJa0ZRU1NCelpYWnRZWEpySWl3aWJtRnRaU0k2SW1GdWRHOXVhVzlqY25WNloyOXRaWE00T0RCQVoyMWhhV3d1WTI5dElpd2laWGh3SWpveE56RXlOemcwTkRjM2ZRLmFHdy1LMkNkR2hjcGkyOTNyYWR0eDdzUW5yMXYySW84Um4wcEtySlVZMm8iLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbkpXVCI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSmhiblJ2Ym1sdlkzSjFlbWR2YldWek9EZ3dRR2R0WVdsc0xtTnZiU0lzSW1semN5STZJa0ZRU1NCelpYWnRZWEpySWl3aWJtRnRaU0k2SW1GdWRHOXVhVzlqY25WNloyOXRaWE00T0RCQVoyMWhhV3d1WTI5dElpd2laWGh3SWpveE56RXlOemcwTkRjM2ZRLmFHdy1LMkNkR2hjcGkyOTNyYWR0eDdzUW5yMXYySW84Um4wcEtySlVZMm8ifQ.C6dBPGnfiGwwN5eYG7TqcrSbS_wBaEyHMfrlOoB2bO8"

O problema então está no envio do Token. Você está testando pelo Insomnia, como demonstrado no curso?

Estou testando no postman, posso baixar o insomnia se achar que isso pode ser o erro, so queria mandar antes este erro que está retornando no console novamente

Falha ao verificar o token JWT: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA256 2024-04-10T16:40:59.633-03:00 ERROR 9356 --- [SevMark] [nio-8080-exec-4] 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: eyJ0b2tlbkpXVCI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSmhiblJ2Ym1sdlkzSjFlbWR2YldWek9EZ3dRR2R0WVdsc0xtTnZiU0lzSW1semN5STZJa0ZRU1NCelpYWnRZWEpySWl3aWJtRnRaU0k2SW1GdWRHOXVhVzlqY25WNloyOXRaWE00T0RCQVoyMWhhV3d1WTI5dElpd2laWGh3SWpveE56RXlOemcwTkRjM2ZRLmFHdy1LMkNkR2hjcGkyOTNyYWR0eDdzUW5yMXYySW84Um4wcEtySlVZMm8iLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbkpXVCI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSmhiblJ2Ym1sdlkzSjFlbWR2YldWek9EZ3dRR2R0WVdsc0xtTnZiU0lzSW1semN5STZJa0ZRU1NCelpYWnRZWEpySWl3aWJtRnRaU0k2SW1GdWRHOXVhVzlqY25WNloyOXRaWE00T0RCQVoyMWhhV3d1WTI5dElpd2laWGh3SWpveE56RXlOemcwTkRjM2ZRLmFHdy1LMkNkR2hjcGkyOTNyYWR0eDdzUW5yMXYySW84Um4wcEtySlVZMm8ifQ.C6dBPGnfiGwwN5eYG7TqcrSbS_wBaEyHMfrlOoB2bO8

Pode testar pelo Postman sem problemas. Só verifica na aba Authorization se está selecionado Bearer Token e confira o token no campo se está certinho:

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

Uma coisa que pode estar causando esse erro é que na sua classe TokenService você está usando Base64 no algoritmo:

var algoritmo = Algorithm.HMAC256(Base64.getDecoder().decode(secret));

Veja se assim resolve o problema (tem que mudar nos dois métodos dessa classe):

var algoritmo = Algorithm.HMAC256(secret);

pronto, era isso mesmo pelo jeito, muito obrigado, tem outro erro sobre as configurações de segurança, que está na parte da autorização das outras endpoints, eu posso falar aqui mesmo ou crio outra pergunta no fórum?

Show! Pode mandar por aqui a outra dúvida

A minha outra dúvida é em relação a segurança na filtragem, está dando erro 403 nas outras requisições, ja fiz alguns dos métodos que voc~e havia colocado la nas aulas mas não funcionou

package com.sevmark.SevMark.config.security;

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.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) {
        try {
            return
                    http.csrf(csrf -> csrf.disable())
                            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                            .authorizeHttpRequests(req -> {
                                req.requestMatchers("/authentication").permitAll();
                                req.anyRequest().authenticated();
                            })
                            .build();
        } catch (Exception e) {
            throw new RuntimeException("Erro na Filtragem: " + e.getMessage());
        }
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Está faltando adicionar o filtro nas configurações de segurança:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private SecurityFilter securityFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) {
        try {
            return
                    http.csrf(csrf -> csrf.disable())
                            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                            .authorizeHttpRequests(req -> {
                                req.requestMatchers("/authentication").permitAll();
                                req.anyRequest().authenticated();
                            })
                            .and().addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                            .build();
        } catch (Exception e) {
            throw new RuntimeException("Erro na Filtragem: " + e.getMessage());
        }
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

no caso, algumas coisas ai entraram em desuso, tanto que no seu curso tem uma aba sobre isso

Se estiver utilizando as últimas versões do Spring Boot, o código deve ficar assim:

@Configuration
@EnableWebSecurity
public class SecurityConfigurations {

    @Autowired
    private final SecurityFilter securityFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers("/authentication").permitAll();
                    req.anyRequest().authenticated();
                })
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

}

No caso o AbstractHttpConfigurer não está sendo reconhecido pelo compilador e está dando erro e não da para importa-lo ou algo do tipo, e como a variável securityFilter está sendo inicializada como final está dando o seguinte erro "Variable 'securityFilter' might not have been initialized"

solução!

funcionou desta forma

package com.sevmark.SevMark.config.security;

import org.springframework.beans.factory.annotation.Autowired;
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.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;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private SecurityFilter securityFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers("/authentication").permitAll();
                    req.anyRequest().authenticated();
                })
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Muito obrigado professor, estava com essas dúvida tem um tempo