Securing Spring Boot API with API key and secret

We Are Going To Discuss About Securing Spring Boot API with API key and secret. So lets Start this Java Article.

Securing Spring Boot API with API key and secret

Advertisements
  1. Securing Spring Boot API with API key and secret

    I realize I am a little late to the game on this one, but I also managed to get API keys working with Spring Boot in tandem with user-name/password authentication. I wasn't crazy about the idea of using AbstractPreAuthenticatedProcessingFilter because in reading the JavaDoc, it seemed like a misuse of that particular class.

  2. Securing Spring Boot API with API key and secret

    I realize I am a little late to the game on this one, but I also managed to get API keys working with Spring Boot in tandem with user-name/password authentication. I wasn't crazy about the idea of using AbstractPreAuthenticatedProcessingFilter because in reading the JavaDoc, it seemed like a misuse of that particular class.

Solution 1

Advertisements

Create a filter that grabs what ever header(s) you’re using for authentication.

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 the filter in your Web Security config.

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();
    }

}

Original Author MarkOfHall Of This Content

Solution 2

Advertisements

I realize I am a little late to the game on this one, but I also managed to get API keys working with Spring Boot in tandem with user-name/password authentication. I wasn’t crazy about the idea of using AbstractPreAuthenticatedProcessingFilter because in reading the JavaDoc, it seemed like a misuse of that particular class.

I ended up creating a new ApiKeyAuthenticationToken class along with a pretty simple raw servlet filter to accomplish this:

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<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.apiKey = apiKey;
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return apiKey;
    }
}

And the filter

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;
    }
}

All that is left at this point is to inject the filter at the proper location in the chain. In my case, I wanted API key authentication to be evaluated before any user-name / password authentication so that it could authenticate the request before the application tried to redirect to a login page:

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

One other thing I will say you should watch out for is that your API key authenticated requests don’t create and abandon a bunch of HttpSessions on your server.

Original Author matt forsythe Of This Content

Solution 3

Advertisements

The answer from the @MarkOfHall is correct and I just want to add a little more details. After you have the code, you will need to add the property values to the application.properties file as below:

yourapp.http.auth-token-header-name=X-API-KEY
yourapp.http.auth-token=abc123

The set the authentication value in the Postman as below:

enter image description here

You can use Postman but if you use cURL request will be similar provided below:

$ curl -H "X-API-KEY: abc123" "http://localhost:8080/api/v1/property/1"

Unless if provide the correct key and value, the app will not work.

Original Author Arefe Of This Content

Conclusion

Advertisements

So This is all About This Tutorial. Hope This Tutorial Helped You. Thank You.

Also Read,

Siddharth

I am an Information Technology Engineer. I have Completed my MCA And I have 4 Year Plus Experience, I am a web developer with knowledge of multiple back-end platforms Like PHP, Node.js, Python and frontend JavaScript frameworks Like Angular, React, and Vue.

Leave a Comment