使用API​​密钥和密钥保护Spring Boot API

时间:2018-01-25 15:42:22

标签: java spring api spring-boot spring-security

我想保护Spring Boot API,因此只有拥有有效API密钥和密钥的客户端才能访问它。但是,程序内部没有身份验证(使用用户名和密码进行标准登录),因为所有数据都是匿名的。我想要实现的是,所有API请求只能用于特定的第三方前端。

我发现了很多关于如何使用用户身份验证保护Spring Boot API的文章。但我不需要用户身份验证。我所想的只是为我的客户提供API密钥和秘密,以便他可以访问端点。

你能告诉我怎样才能做到这一点?谢谢!

3 个答案:

答案 0 :(得分:34)

创建一个过滤器,用于抓取您用于身份验证的标题。

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

}

在网络安全配置中配置过滤器。

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

}

答案 1 :(得分:5)

我意识到我对此游戏有些迟了,但是我还设法使API密钥与用户名/密码认证一起与Spring Boot一起使用。我对使用AbstractPreAuthenticatedProcessingFilter的想法并不感到疯狂,因为在阅读JavaDoc时,这似乎是对该特定类的滥用。

我最终创建了一个新的ApiKeyAuthenticationToken类以及一个非常简单的原始servlet过滤器来完成此任务:

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

还有过滤器

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

这时剩下的就是将过滤器注入链中的适当位置。就我而言,我希望在对任何用户名/密码进行身份验证之前先评估API密钥身份验证,以便它可以在应用程序尝试重定向到登录页面之前对请求进行身份验证:

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

我要提醒您的另一件事是,经过API密钥身份验证的请求不会在服务器上创建并放弃一堆HttpSession

答案 2 :(得分:2)

@MarkOfHall 的回答是正确的,我只想添加更多细节。获得代码后,您需要将属性值添加到 Class2 method() { Class foo = new Class(); if (foo.variable == null) throw new RuntimeException(); Class2 bar = new Class2(); bar.otherVariable = 5; return bar; } 文件中,如下所示:

application.properties

在 Postman 中设置认证值如下:

enter image description here

您可以使用 Postman,但如果您使用 yourapp.http.auth-token-header-name=X-API-KEY yourapp.http.auth-token=abc123 ,请求将类似于以下提供的内容:

cURL

除非提供正确的键和值,否则应用程序将无法运行。