19
respostas

Login

Quando eu vou testar no navegador me manda pra tela de Login! e eu coloquei o SPRING_ACTIVE_PROFILE='prod' mas não resolve. A API está igual a do professor, eu até baixei para garantir que não tivesse nenhum erro.

19 respostas

Não está pegando o perfil como prod, mesmo eu passando SPRING_ACTIVE_PROFILES='prod'

$ docker run -p 8080:8080 -e FORUM_DATABASE_URL='jdbc:h2:mem:alura-forum' -e FORUM_DATABASE_USERNAME='sa' -e FORUM_DATABASE_PASSWORD='' -e FORUM_JWT_SECRET='123456' -e SPRING_ACTIVE_PROFILE='prod' versao/final

. __ _ __ _ _ /\ / _'_ _ _ _()_ _ __ _ \ \ \ ( ( )___ | ' | '| | ' / ` | \ \ \ \/ ___)| |)| | | | | || (| | ) ) ) ) ' |__| .|| ||_| |_, | / / / / =========||==============|__/=//// :: Spring Boot :: (v2.3.1.RELEASE)

2022-04-07 22:00:49.490 INFO 1 --- [ main] br.com.alura.forum.ForumApplication : Starting ForumApplication v0.0.1-SNAPSHOT on fcdc1a9a70c2 with PID 1 (/app.jar started by spring in /) 2022-04-07 22:00:49.506 INFO 1 --- [ main] br.com.alura.forum.ForumApplication : No active profile set, falling back to default profiles: default

Oi Victor,

O nome correto da variável deve ser: SPRING_PROFILES_ACTIVE

Com a nova versão do Spring Boot 3.0 estou tendo problemas com a classe TokenService, no método isTokenValid, parece que é algo relacionado com a dependencia do jwt, parece que a dependencia do jwt não tem mais suporte do jakarta com a nova versão do spring. Gostaria de saber oq fazer. Na classe Security, já troquei os antMatchers para requestMatchers mas n resolveu. Na IDE diz que está deprecated, gostaria de saber como corrigir.

Postei essa dúvida em outro lugar, acho q aqui era o local correto.

Oi Victor!

A recomendação é utilizar essa outra lib:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.2.1</version>
</dependency>

E atualizar o código da classe TokenService:

@Service
public class TokenService {

    @Value("${forum.jwt.secret}")
    private String secret;

    public String gerarToken(Authentication authentication) {
        try {
            Usuario logado = (Usuario) authentication.getPrincipal();
            Algorithm algoritmo = Algorithm.HMAC256(secret);
            return JWT.create()
                    .withIssuer("API do Fórum da Alura")
                    .withSubject(logado.getId().toString())
                    .withExpiresAt(dataExpiracao())
                    .sign(algoritmo);
        } catch (JWTCreationException exception){
            throw new RuntimeException("erro ao gerar token jwt", exception);
        }
    }

    public String getSubject(String tokenJWT) {
        try {
            Algorithm algoritmo = Algorithm.HMAC256(secret);
            return JWT.require(algoritmo)
                    .withIssuer("API do Fórum da Alura")
                    .build()
                    .verify(tokenJWT)
                    .getSubject();
        } catch (JWTVerificationException exception) {
            throw new RuntimeException("Token JWT inválido ou expirado!");
        }
    }

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

Como ficaria o Filter então? Outra dúvida, Ao inves de utilizar esse método dataExpiração posso continuar usando como antes como variavel de ambiente?

Pode continuar sim usando o expiration. Nesse exemplo que mandei está com expiração de duas horas apenas.

O Filter:

public class AutenticacaoViaTokenFilter extends OncePerRequestFilter {

    private TokenService tokenService;
    private UsuarioRepository repository;

    public AutenticacaoViaTokenFilter(TokenService tokenService, UsuarioRepository repository) {
        this.tokenService = tokenService;
        this.repository = repository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String tokenJWT = recuperarToken(request);

        if (tokenJWT != null) {
            String subject = tokenService.getSubject(tokenJWT);
            Usuario usuario = repository.getReferenceById(Long.parseLong(subject));

            Authentication authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

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

        return null;
    }

}

Agora estou tendo outro problema com a nova versão do Spring.

Quando eu acesso um recurso(uri) que não existe e eu não estou logado, me retorna erro 401, ok,tudo certo! Mas quando eu estou logado e acesso um recurso que não existe, continua retornando 401 ao inves de 404. Não sei o motivo disso estar acontecendo, isso começou a acontecer quando eu mudei pra versão 3.0 do Spring Boot.

 http.authorizeHttpRequests()
                .requestMatchers("/auth","/users/register").permitAll()
                .requestMatchers("/users" , "/users/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and().cors()
                .and().headers().frameOptions().disable()
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS
                .and().addFilterBefore(new AuthenticationJWTFilter(tokenService, userRepository), UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler())
                .and().exceptionHandling().accessDeniedHandler(new ForbiddenHandler());

Vai dependeder de como está sua classe RestControllerAdvice em relação ao tratamento de exceptions.

A classe RestControllerAdvice minha não captura exception de not found , exemplo : localhost:8080/recursoinválido . Poderia me ajudar a como fazer o spring capturar isso então? Pq ele está retornando 401 e n 404. Mesmo quando eu estou logado,aparece 401 e meu security está anyRequests().authenticated();

Com a implementação que você fez no curso, isso funciona certinho. Quando acesso um recurso (uri) que não existe, e eu estou logado, retorna 404 , nessa nova implementação que você passou na resposta da nova dependencia do JWT, na classe TokenService e filter, isso agora começou a bugar na minha aplicação, mesmo quando estou logado em um recurso que não existe, volta o erro 401 ao inves do 404. O meu HandlerAdvice, eu não alterei nada mesmo, oq tá falhando é a nova implementação do token.

Tenho certeza, acabei de testar novamente, oq está bugando é a nova implementação que você passou na resposta. Não sei se é na classe filter ou tokenservice

Um handler para tratar os erros:

@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(DataIntegrityViolationException.class)
    public ResponseEntity tratarErro400(DataIntegrityViolationException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity tratarErro500(Exception ex) {
        return ResponseEntity.internalServerError().body(ex.getMessage());
    }

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

}

Não funcionou, continua com o erro 401!

Quando estou sem nenhum usuário logado :

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

Ok, está funcionando corretamente, mas olha essa outra print :

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

Mesmo quando eu passo um token VÁLIDO, o erro continua sendo 401 ao inves de 404, isso começou a acontecer com a nova implementação que você passou, isso não tem relação com o handler, o problema está acontecendo antes de chegar no Handler, o problema é no filtro ou no tokenservice. Você poderia conferir isso em algum projeto seu pfv? com a implementação que você me passou.

O erro é muito esquisito pois o erro continua dando 401 mesmo com o usuário autenticado :

Insira aqui a descrição dessa imagem para ajudar na acessibilidade É possivel observar que o método recupera o usuário logado, tem um usuário logado ali, mas quando faz o filterChain.doFilter ele desloga automaticamente quando o recurso é inválido, muito esquisito.

Alguém sabe resolver?

Talvez seja por conta dessas classes que você configurou:

.exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler())
.and().exceptionHandling().accessDeniedHandler(new ForbiddenHandler());

Posta aqui o código dessas duas classes

Pior que não, mesmo quando eu comento essas 2 linhas, continua com o problema.

Se puder dar uma olhada aqui : https://github.com/Almadavic/security-standard

Para o Spring devolver 404 quando chegar um requisição para uma url não mapeada você vai precisar sobrescrever essas propriedades:

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

E adicionar no @RestControllerAdvice o tratamento para a exception NoHandlerFoundException:

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

Fiz, deu certinho. Você poderia me explicar por que isso está ocorrendo na nova versão? Sendo que antes não ocorria?

Foi uma mudança que fizeram no spring. Agora esse é o novo padrão.

Resolvido.