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

Erro 401 unauthorized

Segui todos os passos da aula e inclusive baixei o repositório para comparar com o meu código, todos os códigos relacionados a segurança estão iguais ao do professor mas mesmo assim continua com o erro 401. Creio que o erro possa ser algo relacionado a classe que desabilita o csrf, pois ela deveria permitir as requisições, e no código do professor removendo esse método que desabilita o csrf também da o mesmo erro 401.

18 respostas

Oi!

Manda aqui as suas classes SecurityConfigurations, SecurityFilter e TokenService.

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

Suas configurações de segurança não estão liberando nenhuma url, então qualquer requisição vai ser bloqueada.

Como resolve isso ?

Pode seguir com o curso, que vai ser mostrado como implementar a parte de autenticação e depois vai ser liberado a url de login e bloqueada as outras para usuários logados.

você esqueceu de autorizar as requisições para o usuário do token isso é bem fácil de se resolver, acontece que na classe onde você está fazendo a autenticação você precisa dizer pro spring que ele deve permitir a requisição do usuario do token


                tokenService.validateToken(token);

                User user = tokenService.getSecretOwner();
                var auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());

                SecurityContextHolder.getContext().setAuthentication(auth);

seria algo mais ou menos assim no "tokenService.validateToken(token);" estou validando para ver se o token é valido se não houver nada de errado com ele eu utilizo um método na minha "tokenService.getSecretOwner" que serve para encontrar o meu usuario no meu banco de dados atravéz de seu login que foi passado pelo token que eu desencriptei para recuperá-lo e usar um "SecurityContextHolder.getContext().setAuthentication(auth);" para poder dizer para o springBoot que esse usuario foi authenticado (já que não houve nenhuma exception na hora de validar o token)

PS: testa aí e dps me diz se funcionou

Eu não entendi direito o seu código, pois o meu tokenService não tem os métodos validateToken e getSecretOwner.

o método validateToken faz a validação do token baseado no código do professor que foi passado em aula onde eu tenho um código que verifica o tokken e lança uma exception caso esteja errado ou invalido, e o get secre owner serve para pegar o usuario o qual pertence o token já que a chave secreta que irá validar o token irá depender de eu ser capaz de buscar o usuario e utilizar sua senha codificada como secret

package med.voll.api.infra.service.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.Getter;
import med.voll.api.domain.model.user.User;
import med.voll.api.infra.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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


@Service
public class TokenService {

    @Autowired
    private UserRepository userRepository;

    private String secret;
    @Getter
    private User secretOwner;

    private void setSecret(String token) {
        try {
            DecodedJWT decodedJWT = JWT.decode(token);
            String login = decodedJWT.getSubject();
            secretOwner = (User) userRepository.findByLogin(login);
            this.secret = secretOwner.getPassword();

        } catch (JWTDecodeException e) {
            throw new RuntimeException("Erro ao decodificar o token JWT: " + e.getMessage());
        }
    }

    /*gera o token a partir dos dados do usuario*/
    public String generateToken(User user) {
        try {
            this.secret = user.getPassword();
            Algorithm algorithm = Algorithm.HMAC256(secret);

            Instant expireAt = this.expire();

            return JWT.create()
                    .withIssuer("API voll.med")
                    .withSubject(user.getUsername())
                    .withClaim("id", user.getId())
                    .withExpiresAt(Date.from(expireAt))
                    .sign(algorithm);
        } catch (Exception e) {
            throw new RuntimeException("Erro ao gerar o TOKEN: " + e.getMessage());
        }
    }

    /*  utiliza a password disponibilizada pelo getSecret e o usa para setar o algoritimo
     *   para permitir a verificação do token passado*/
    public void validateToken(String token) {

        try {
            this.setSecret(token);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("API voll.med")
                    .build();

            verifier.verify(token);
        } catch (JWTVerificationException e) {
            throw new RuntimeException("Erro ao validar o token: "+e);
        }
    }

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

(apenas uma observação (não faça igual, a minha estrutura de código é diferente da sua, pegue isso como base e desenvolva por si próprio) o que você precisa mesmo é retornar o usuário a qual o token pertence e usá-lo aqui)

`var auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);

Obrigado pela ajuda mas ainda não estou conseguindo entender, vou tentar revisar o conteudo para ver se perdi alguma coisa, mas obrigado

Professor Rodrigo, se o senhor puder responder a minha dúvida, você disse que o problema do meu código era ele estar bloqueando todas as urls de requisições, mas eu já assisti a aula que você mostra como liberar as urls para fazerem as requisições e o meu código continuou dando erro 401.

@Configuration
@EnableWebSecurity
public class SecurityConfigurations {

    @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("/login").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();
    }
    
}

Segue meu código acima, aguardo uma resposta pois é muito frustrante além do curso ter bastante conteúdo eu não conseguir implementá-lo no meu programa.

Oi Vitor!

O código da classe de segurança está correto.

Qual requisição você está disparando e que está causando o erro 401?

Todas, nenhuma requisição está passando, eu já tentei alterar esse código para tentar permitir qualquer requisição, a única exceção é quando eu tento passar uma requisição com um token no header, ele retorna 400 bad request.

Dispare a requisição para efetuar login (POST /login), sem enviar headers e enviando apenas o json com o login/senha cadastrado na tabela de usuários.

Acontece o mesmo erro, nenhuma requisição passa, independente do conteúdo do json no corpo ou dos headers, só retorna 401 unauthorized e se eu envio token no header retorna sempre 400 bad request.

Consegue compartilhar seu projeto? Pode ser via GitHub

solução!

O problema é que você criou a classe main do projeto (ApiApplication) dentro de um pacote chamado App e o Sprign somente vai carregar as classes que estiverem dentro desse pacote ou algum subpacote dele.

Mas todas as outras classes do seu projeto estão em outra estrutura de pacotes distinta.

Obrigado concertei isso e deu alguns outros probleminhas mas consegui resolver, o código funcionou normalmente.