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

[Dúvida] Erro 403 Forbidden em todos os métodos, exceto o login

Opa, boa tarde!

Estou concluindo o curso e estou nas versões atuais do Spring, diferente do projeto. Eu consigo recuperar o token no /login, mas ao tentar acessar outra requisição mesmo com o token no cabeçalho, ele retorna 403 forbidden.

SecurityConfigurations:

@Configuration
@EnableWebSecurity //personalizar as config de segurança
public class SecurityConfigurations {

    @Autowired
    private SecurityFilter securityFilter;

    @Bean //devolve um objeto para o spring
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //stateless é um tipo de estado do servidor, ele não armazena nenhuma memória no programa
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers("/login").permitAll();
                    req.anyRequest().authenticated();
                })
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean //serve para ensinar o spring a injetar um objeto (authenticationManager) que vamos utilizar internamente
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean //config para hash de senha
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

TokenService:

//Classe de validação e geração do token
@Service
public class TokenService {

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

    private static final String ISSUER = "API Voll.med";


    public String gerarToken(UsuarioEntity usuario) {
        try {
            var algoritmo = Algorithm.HMAC256(secret);
            return JWT.create()
                    //issuer -> o "nome" da assinatura do token
                    .withIssuer(ISSUER)
                    //subject -> usuário que é responsavel pelo token
                    .withSubject(usuario.getLogin())
                    //expiresAt -> método para expiração do token
                    .withExpiresAt(dataExpiracao())
                    .sign(algoritmo);
        } catch (JWTCreationException exception){
            throw new RuntimeException("Erro ao gerar o token JWT!", exception);
        }
    }

    public String validarToken(String tokenJWT) {
        try {
            var algoritmo = Algorithm.HMAC256(secret);
            return JWT.require(algoritmo)
                    .withIssuer(ISSUER)
                    .build()
                    //Verifica se o token está válido de acordo com o algoritmo e o issuer
                    .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"));
    }

}

SecurityFilter:

//Herdamos uma classe do Spring para fazer o filtro
@Component
public class SecurityFilter extends OncePerRequestFilter {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UsuarioRepository repository;

    //Será executada a cada uma vez pra cada requisição
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //Código para recuperar e validar o token

        var tokenJWT = recuperarToken(request);

        if (tokenJWT.isEmpty()) {
            var subject = tokenService.validarToken(tokenJWT);
            var usuario = repository.findByLogin(subject);

            var authentication = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        //Chama o próximo filtro para seguir o fluxo da aplicação
        filterChain.doFilter(request, response);
    }

    private String recuperarToken(HttpServletRequest request) {
        //Recupera o cabeçalho authorization onde está o token
        var authorizationHeader = request.getHeader("Authorization");

        if(authorizationHeader != null) {
            return authorizationHeader.replace("Bearer", "");
        }

        return "Cabeçalho Authorization vazio ou nulo!";
    }


}

Desde já, muito obrigado!

2 respostas
solução!

Oi!

O problema está no seu SecurityFilter, no método recuperarToken. Ele deveria devolver null quando não vier um token no cabeçalho:

private String recuperarToken(HttpServletRequest request) {
    //Recupera o cabeçalho authorization onde está o token
    var authorizationHeader = request.getHeader("Authorization");

    if(authorizationHeader != null) {
        return authorizationHeader.replace("Bearer ", "");
    }

    return null;
}

Além disso, faltou o espaço em branco, no replace, após a palavra Bearer.

Altere também o if do método doFilterInternal:

if (tokenJWT != null) {

Funcionou certinho, não tinha percebi isso. Muito obrigado, professor!