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

Gera o token, mas retorna login nulo

Estou utilizando os conhecimentos adquiridos no curso para programar uma aplicação Full Stack e estou enfrentando a seguinte dificuldade: Ao tentar efetuar o login de um usuário devidamente salvo no banco, retorna o token, mas informa que o usuário é nulo. Inseri 'System.out.println' em 'token', 'login' e 'user' para facilitar o rastreamento. Segue a mensagem de erro: Insira aqui a descrição dessa imagem para ajudar na acessibilidade. Meu arquivo SecurityConfigurations: `package com.minascafe.api.infra.security;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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 SecurityConfigurations {

@Autowired
SecurityFilter securityFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity                
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .cors()
            .and()
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/auth/login/**").permitAll()
                    .requestMatchers(HttpMethod.POST, "/auth/register").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.OPTIONS, "/auth/register").permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/cafecoco/**").permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/cafemaquina/**").permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/cafebeneficiado/**").permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/produtor/**").permitAll()
                    .requestMatchers(HttpMethod.POST, "/cafecoco/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.POST, "/cafemaquina/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.POST, "/cafebeneficiado/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.POST, "/produtor/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.GET, "/cafecoco/**").authenticated()
                    .requestMatchers(HttpMethod.GET, "/cafemaquina/**").authenticated()
                    .requestMatchers(HttpMethod.GET, "/cafebeneficiado/**").authenticated()
                    .requestMatchers(HttpMethod.GET, "/produtor/**").authenticated()
                    .requestMatchers(HttpMethod.PUT, "/cafecoco/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.PUT, "/cafemaquina/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.PUT, "/cafebeneficiado/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.PUT, "/produtor/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.DELETE, "/cafecoco/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.DELETE, "/cafemaquina/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.DELETE, "/cafebeneficiado/**").hasRole("ADMIN")
                    .requestMatchers(HttpMethod.DELETE, "/produtor/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
            )
            .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

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

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

}`.

8 respostas

SecurityFilter:

package com.minascafe.api.infra.security;

import com.minascafe.api.entities.User;
import com.minascafe.api.repositories.UserRepository;
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.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;

@Component
public class SecurityFilter extends OncePerRequestFilter {
    @Autowired
    TokenService tokenService;
    @Autowired
    UserRepository userRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = this.recoverToken(request);
        System.out.println("*******************   O conteúdo de token é: " + token +"   ***********************");
        if (token != null) {
            String login = tokenService.validateToken(token);
            System.out.println("*******************   O conteúdo de login é: " + login +"   ***********************");
            
            UserDetails user = userRepository.findByLogin(login); // Encontra o usuário
            System.out.println("*******************   O conteúdo de user é: " + user +"   ***********************");
            
            var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            
            SecurityContextHolder.getContext().setAuthentication(authentication);// Considere este usuário já logado
        }
        filterChain.doFilter(request, response);
    }

    private String recoverToken(HttpServletRequest request) {
        var authHeader = request.getHeader("Authorization");
        if (authHeader == null){
        
           return null;
        }
            
        return authHeader.replace("Bearer ", "");
    }
}

TokenService:

package com.minascafe.api.infra.security;

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.minascafe.api.entities.User;
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 {    // ***** Classe para geração dos Tokens *****

    @Value("${api.security.token.secret}") //Variavel de ambiente - Está no application.properties
    private String secret;

     public String generateToken(User user){
         try{
             Algorithm algorithm = Algorithm.HMAC256(secret);
             String token = JWT.create()
                     .withIssuer("MinasCafe")
                     .withSubject(user.getLogin())
                     .withExpiresAt(genExpirationDate())
                     .sign(algorithm);
             return token;
         } catch (JWTCreationException exception){
             throw new RuntimeException("Erro ao gerar token JWT!"+ exception.getMessage());
         }
    }

    //Método para verificar se o token ainda está válido p/ qdo usuário fizer uma nova requisição
    public String validateToken(String token) { // Descriptografa e pega o "subject": usuário
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            return JWT.require(algorithm)
                    .withIssuer("MinasCafe") //Quem foi o emissor - "nome da aplicação"
                    .build() //construindo novamente o nosso token
                    .verify(token)//descriptografando o token
                    .getSubject(); //pegando o usuário que foi adicionado no token
        } catch (JWTVerificationException exception) {
            return "";
        }
    }

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

}

AuthorizationService:

package com.minascafe.api.services;

import com.minascafe.api.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class AuthorizationService implements UserDetailsService {

    @Autowired
    UserRepository repository;
    
    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        return repository.findByLogin(login);
    }
}
```.
Alguém pode me dar uma sugestão?

Oi!

Coloca um system.out nesse catch da classe TokenService para ver a exception que está acontecendo:

public String validateToken(String token) { // Descriptografa e pega o "subject": usuário
    try {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        return JWT.require(algorithm)
                .withIssuer("MinasCafe") //Quem foi o emissor - "nome da aplicação"
                .build() //construindo novamente o nosso token
                .verify(token)//descriptografando o token
                .getSubject(); //pegando o usuário que foi adicionado no token
    } catch (JWTVerificationException exception) {
        System.out.println("Erro ao obter usuário do token: " +token);
        e.printStackTrace();
        
        return "";
    }
}

E veja no console a exception que vai aparecer.

Inseri e o resultado foi este: Insira aqui a descrição dessa imagem para ajudar na acessibilidade ). Rodrigo, constatei aqui que quando eu realizo por meio do Insomnia a requisição do tipo 'POST', inserindo por meio de Json, os dados 'login' e 'senha', retorna o token corretamente. Se eu copio esse token e insiro nas outras requisições que necessitam de autenticação, elas também retornam o resultado esperado. Esse problema está acontecendo quando eu estou chamando o endpoint de login a apartir de um FrontEnd desenvolvido em React. Sei que este curso aborda somente o BackEnd, mas eu preciso integrá-lo a um projeto real, full stack no caso, onde eu acredito que estou chamando o endpoint corretamente e envindo os dados necessários via Json.

Entendi. O problema então está no envio do token pelo frontend.

Pelo print da exception, está chegando o token certinho, mas a biblioteca está considerando inválido. Eu acho que pode ser que ele esteja vindo com um espaço em branco a mais no ínicio ou no fim da String.

Altere o seu método recoverToken da classe SecurityFilter para remover esses espaços:

private String recoverToken(HttpServletRequest request) {
    var authHeader = request.getHeader("Authorization");
    if (authHeader == null){

       return null;
    }

    // chamando trim() para remover espaços em branco no inicio e no fim da String
    return authHeader.replace("Bearer ", "").trim();
}

Veja se com isso resolve.

Após alterar o método RecoverToken, conforme sugerido acima, está retornando da seguinte forma: Insira aqui a descrição dessa imagem para ajudar na acessibilidade ).

Rodrigo, não sei se você entende de React, estou chamando o método no Front da seguinte maneira: `const handleLogin = async () => { if (!user || !password) { setErrorMessage("Preencha corretamente o usuário e a senha para realizar o login!"); return; }

try{        
    const response = await axios.post('http://localhost:8080/auth/login', {
      login: user,
      senha: password
    }, {
         headers: {
        'Content-Type': 'application/json'
       }
    });        

    if (response.status === 200) {
      // Se a autenticação for bem sucedida, armazenar o token JWT no localStorage
      localStorage.setItem('token', response.data.token);

      // Redireciona para a página inicial
      history.push('/paginainicial');
    } else {
      const errorMessage = response.data.message;// await response.text(); - supondo que o erro retorne o corpo da resposta
      //throw new Error(response.data.message);
    }
      
} catch (error){
  //Trate qualquer erro aqui
  console.error('Erro ao fazer login: ', error);
  setErrorMessage("Usuário ou senha incorretos, tente novamente!");
}

}`. Será que há algum erro na chamada do endpoint?

O código está correto.

Mas o problema é o token sendo enviado no frontend. Parece que ele está lendo outro token de outra aplicação.

Eu peguei o token que você mandou no print e joguei no site https://jwt.io para ver os dados dele:

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

Repare que no issuer está como auth-api, mas no código da sua API, na classe TokenService, foi colocado no token MinasCafe como issuer.

Por isso está dando exception. Eu acredito que seja por que a aplicação frontend esteja lendo o token incorreto, antes de enviá-lo na requisição. Parece estar lendo o token de outra aplicação distinta.

solução!

Rodrigo, o problema estava no FrontEnd na chamada 'Axios' ao EndPoint:

const response = await axios.post('http://localhost:8080/auth/login', {
        login: user,
        senha: password
        }, {
        headers: {
        'Content-Type': 'application/json'
        }
        });

Substituí por uma chamada 'fetch':

const response = await fetch('http://localhost:8080/auth/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    login: user,
    senha: password
  })
});

E funcionou normalmente.De qualquer forma o desenvolvimento da parte de autenticação e autorização, conforme aprendido aqui no curso, estava correto e funcionando perfeitamente. Agradeço muito Rodrigo a sua atenção e auxílio!