Securing Spring Boot API with API key and secret

后端 未结 2 717
滥情空心
滥情空心 2020-12-12 12:43

I would like to secure the Spring Boot API so it is accessible only for the clients that has valid API key and secret. However, there is no authentication (standard login wi

2条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-12 13:06

    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 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.

提交回复
热议问题