Ya estoy inscrito ¿Todavía no tienes acceso? Nuestros Planes
Ya estoy inscrito ¿Todavía no tienes acceso? Nuestros Planes
3
respuestas

[Bug] codigo 403 forbiden en insomnia

hola a todos, he seguido todo el curso con detalle pero, me encontre con varias cosas confusas como metodos depreciados, lo cual no se como solucionar, actualmente estoy en un punto que no se porque no me arroja los resultados experados. si alguien puede ayudarme se los agradeceria.

anexo parte de mi codigo: estas son las clase que estoy implementando

package med.voll.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.annotation.web.configurers.AbstractHttpConfigurer;
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
// le hacemos saber a Spring que tenemos un metodo en el contexto de seguridad
@EnableWebSecurity
public class SecurityConfigurations {

    // agregando filtro personalizado como dependencia
    @Autowired
    private SecurityFilter securityFilter;


    // nesecitamos retornar un tipo de obeto para poder implementar el concepto de statles
    // este metodo sobre escribe el comportamiento de Spring security
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers(HttpMethod.POST, "/login").permitAll();
                    req.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();
    }
}
3 respuestas

clase SecurityFilter

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import med.voll.api.domain.usuarios.UsuarioRepository;
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 {

    // inyectando dependencia de mi token service
    @Autowired
    private TokenService tokenService;

    // inyectamos dependencia de nuestro repo para forzar un inicio de sesion
    @Autowired
    private UsuarioRepository usuarioRepository;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

//        System.out.println("this is the begining of the filter");
        // obtener el token del header
        var autHeader  = request.getHeader("authorization");//.replace("Bearer ", "")
        // prueba del token
        System.out.println(autHeader);

        if(autHeader != null ){
            //System.out.println("validamos que el token no es null");
           var token = autHeader.replace("Bearer ", "");
            var nombreUsuario = tokenService.getSubject(token);

            // forzamos un login de un usuario en cada request debido a que estamos utilizando un tipo de sesion STATELESS,
            //para poder usar  UsernamePasswordAuthenticationFilter.class ( que valida si mi usuario tiene un inicio de sesion)
            if(nombreUsuario != null ){
                // token es valido
                var usuario = usuarioRepository.findByLogin(nombreUsuario);

                // convertimos ese usuario en un objeto authentication
                var authentication = new UsernamePasswordAuthenticationToken(usuario, null,
                        usuario.getAuthorities()); // forzamos el inicio de sesion
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);

    }
}

clase TokenService

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import med.voll.api.domain.usuarios.Usuario;
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 {

    // propiedad de mi aplications properties
    @Value("${api.security.secret}")
    private String apiSecret;

    public String generarToken(Usuario usuario){

        try {
            Algorithm algorithm = Algorithm.HMAC256(apiSecret);
            return JWT.create()
                    .withIssuer("voll med")
                    .withSubject(usuario.getLogin())
                    .withClaim("id", usuario.getId())
                    .withExpiresAt(generarFechaExpiracionToken())
                    .sign(algorithm);
        } catch (JWTCreationException exception){
            throw new RuntimeException();
        }
    }

    public String getSubject(String token) {

        // valido si el token ´no esta presente
        if(token == null){
            throw new RuntimeException();
        }
        DecodedJWT verifier = null;
        try {
            Algorithm algorithm = Algorithm.HMAC256(apiSecret);// valido firma del token
            verifier = JWT.require(algorithm)
                    // specify any specific claim validations
                    .withIssuer("voll med")
                    // reusable verifier instance
                    .build()
                    .verify(token);
            verifier.getSubject();
        } catch (JWTVerificationException exception) {
            // Invalid signature/claims
            System.out.println(exception.toString());
        }


        if (verifier.getSubject() == null){
            throw new RuntimeException("verifier invalido");
        }
        return verifier.getSubject();

    }

    // Metodo que retorna el tiempo de expiracion del token

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

anexo logs de mi consola

2025-01-12T13:01:20.758-05:00  INFO 12276 --- [  restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-01-12T13:01:20.766-05:00  INFO 12276 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-01-12T13:01:21.390-05:00  INFO 12276 --- [  restartedMain] r$InitializeUserDetailsManagerConfigurer : Global AuthenticationManager configured with UserDetailsService bean with name autenticacionService
2025-01-12T13:01:21.558-05:00  WARN 12276 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-01-12T13:01:22.422-05:00  INFO 12276 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2025-01-12T13:01:22.468-05:00  INFO 12276 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-01-12T13:01:22.484-05:00  INFO 12276 --- [  restartedMain] med.voll.api.ApiApplication              : Started ApiApplication in 8.705 seconds (process running for 9.482)
2025-01-12T13:10:54.658-05:00  INFO 12276 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-01-12T13:10:54.658-05:00  INFO 12276 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-01-12T13:10:54.666-05:00  INFO 12276 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
null
Hibernate: 
    select
        u1_0.id,
        u1_0.clave,
        u1_0.login 
    from
        usuarios u1_0 
    where
        u1_0.login=?

Hola Julio, espero que estés bien

Una pregunta: ¿El usuario y la contraseña están presentes en la base de datos? ¿Podría mostrar el error de la pantalla de solicitud en insomnia?

Te lo dejo abajo una sugerencia de mejora para su código, para tratar algunos errores etc.

@Component
public class SecurityFilter extends OncePerRequestFilter {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UsuarioRepository usuarioRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String authHeader = request.getHeader("Authorization");

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.replace("Bearer ", "");

            try {
                String username = tokenService.getSubject(token);

                if (username != null) {
                    usuarioRepository.findByLogin(username).ifPresent(usuario -> {
                        var authentication = new UsernamePasswordAuthenticationToken(
                                usuario,
                                null,
                                usuario.getAuthorities()
                        );
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    });
                }
            } catch (Exception e) {
                // Registro de errores y respuesta adecuada en caso de token inválido
                logger.error("Error al procesar el token: {}", e.getMessage());
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

Cambios realizados:

  1. Validación del encabezado: Verifica explícitamente que el encabezado comience con "Bearer ".
  2. Uso de Optional: Utiliza Optional para manejar el resultado de usuarioRepository.findByLogin.
  3. Logger: Reemplaza System.out.println con un logger para un manejo profesional de registros.
  4. Control de excepciones: Agrega un try-catch para manejar posibles errores al procesar el token.
  5. Claridad del código: Cambia nombres de variables para mejorar la legibilidad (e.g., autHeader -> authHeader).
  6. Respuesta para tokens inválidos: Si el token es inválido, responde con un estado 401 Unauthorized.

Saludos