Saltar al contenido

Protección de la API Spring Boot con API key y secreto

Tenemos la mejor solución que hemos encontrado por todo internet. Nosotros esperamos que te sea de mucha utilidad y si deseas compartir cualquier detalle que nos pueda ayudar a crecer hazlo con total libertad.

Solución:

Cree un filtro que tome los encabezados que esté utilizando para la autenticación.

import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter 

    private String principalRequestHeader;

    public APIKeyAuthFilter(String principalRequestHeader) 
        this.principalRequestHeader = principalRequestHeader;
    

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) 
        return request.getHeader(principalRequestHeader);
    

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) 
        return "N/A";
    


Configure el filtro en su configuración de seguridad web.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

@Configuration
@EnableWebSecurity
@Order(1)
public class APISecurityConfig extends WebSecurityConfigurerAdapter 

    @Value("$yourapp.http.auth-token-header-name")
    private String principalRequestHeader;

    @Value("$yourapp.http.auth-token")
    private String principalRequestValue;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
        APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
        filter.setAuthenticationManager(new AuthenticationManager() 

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException 
                String principal = (String) authentication.getPrincipal();
                if (!principalRequestValue.equals(principal))
                
                    throw new BadCredentialsException("The API key was not found or not the expected value.");
                
                authentication.setAuthenticated(true);
                return authentication;
            
        );
        httpSecurity.
            antMatcher("/api/**").
            csrf().disable().
            sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
            and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
    


Me doy cuenta de que llegué un poco tarde al juego en este caso, pero también logré obtener API keys trabajar con Spring Boot junto con la autenticación de nombre de usuario/contraseña. No estaba loco por la idea de usar AbstractPreAuthenticatedProcessingFilter porque al leer el JavaDoc, parecía un mal uso de esa clase en particular.

Terminé creando una nueva ApiKeyAuthenticationToken class junto con un filtro de servlet sin procesar bastante simple para lograr esto:

import java.util.Collection;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;

@Transient
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken 

    private String apiKey;
    
    public ApiKeyAuthenticationToken(String apiKey, Collection authorities) 
        super(authorities);
        this.apiKey = apiKey;
        setAuthenticated(true);
    

    @Override
    public Object getCredentials() 
        return null;
    

    @Override
    public Object getPrincipal() 
        return apiKey;
    

y el filtro

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;

public class ApiKeyAuthenticationFilter implements Filter 

    static final private String AUTH_METHOD = "api-key";
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    
        if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) 
            String apiKey = getApiKey((HttpServletRequest) request);
            if(apiKey != null) 
                if(apiKey.equals("my-valid-api-key")) 
                    ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
                    SecurityContextHolder.getContext().setAuthentication(apiToken);
                 else 
                    HttpServletResponse httpResponse = (HttpServletResponse) response;
                    httpResponse.setStatus(401);
                    httpResponse.getWriter().write("Invalid API Key");
                    return;
                
            
        
        
        chain.doFilter(request, response);
        
    

    private String getApiKey(HttpServletRequest httpRequest) 
        String apiKey = null;
        
        String authHeader = httpRequest.getHeader("Authorization");
        if(authHeader != null) 
            authHeader = authHeader.trim();
            if(authHeader.toLowerCase().startsWith(AUTH_METHOD + " ")) 
                apiKey = authHeader.substring(AUTH_METHOD.length()).trim();
            
        
        
        return apiKey;
    

Todo lo que queda en este punto es inyectar el filtro en la ubicación adecuada de la cadena. En mi caso, quería API key la autenticación se evaluará antes de cualquier autenticación de nombre de usuario/contraseña para que pueda autenticar la solicitud antes de que la aplicación intente redirigir a una página de inicio de sesión:

@Override
protected void configure(HttpSecurity http) throws Exception 
    http
        .csrf()
            .disable()
        .addFilterBefore(new ApiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .authorizeRequests()
            .anyRequest()
                .fullyAuthenticated()
                .and()
        .formLogin();

Otra cosa que diré que debe tener en cuenta es que su API key las solicitudes autenticadas no crean y abandonan un montón de HttpSessions en su servidor.

Sección de Reseñas y Valoraciones

Finalizando este artículo puedes encontrar las notas de otros usuarios, tú igualmente tienes el poder dejar el tuyo si lo deseas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *