Java 使用API密钥和密码保护Spring Boot API

Java 使用API密钥和密码保护Spring Boot API,java,spring,api,spring-boot,spring-security,Java,Spring,Api,Spring Boot,Spring Security,我想保护SpringBootAPI,使其仅对具有有效API密钥和密码的客户端可访问。但是,由于所有数据都是匿名的,程序内部没有身份验证(使用用户名和密码的标准登录)。我试图实现的是,所有API请求只能用于特定的第三方前端 我找到了很多关于如何使用用户身份验证保护Spring引导API的文章。但我不需要用户身份验证。我所想的只是向我的客户机提供API密钥和机密,以便他能够访问端点 你能建议我怎样才能做到这一点吗?谢谢大家! 创建一个过滤器,用于获取用于身份验证的任何标头 import org.sp

我想保护SpringBootAPI,使其仅对具有有效API密钥和密码的客户端可访问。但是,由于所有数据都是匿名的,程序内部没有身份验证(使用用户名和密码的标准登录)。我试图实现的是,所有API请求只能用于特定的第三方前端

我找到了很多关于如何使用用户身份验证保护Spring引导API的文章。但我不需要用户身份验证。我所想的只是向我的客户机提供API密钥和机密,以便他能够访问端点


你能建议我怎样才能做到这一点吗?谢谢大家!

创建一个过滤器,用于获取用于身份验证的任何标头

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

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

}

我意识到我在这个游戏上有点晚了,但我也设法获得了与Spring Boot配合使用的API密钥以及用户名/密码身份验证。我对使用
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;
    }
}
此时剩下的就是在链中的适当位置注入过滤器。在我的例子中,我希望在任何用户名/密码身份验证之前评估API密钥身份验证,以便在应用程序尝试重定向到登录页面之前对请求进行身份验证:

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

我要说的另一件事是,您应该注意的是,您的API密钥验证请求不会在您的服务器上创建和放弃一堆
HttpSession

来自@MarkOfHall的答案是正确的,我只想补充一点细节。获得代码后,需要将属性值添加到
application.properties
文件中,如下所示:

yourapp.http.auth-token-header-name=X-API-KEY
yourapp.http.auth-token=abc123
在邮递员中设置身份验证值,如下所示:

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

您可以使用邮递员,但如果您使用
cURL
请求将类似于以下内容:

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

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

唯一的区别是您将其称为API密钥而不是用户名,还是有其他区别?下面的答案是否解决了您的问题?您是如何管理您的用户和每个用户的api密钥的?这非常有用。我有一个应用程序,它需要同时支持用户名/密码和基于ApiKey的身份验证。我有用户名/密码工作,并在阅读您的帖子后,能够得到ApiKey工作。不幸的是,我似乎破坏了用户名/密码。我怀疑这是因为我的过滤器排序或我对用户名/密码和ApiKey身份验证使用了相同的AuthenticationManager。有什么建议吗?@PhillipStack您应该能够使用不同的身份验证管理器配置两个WebSecurity配置适配器。ala:如果我理解正确,APIKey不是私有的。任何使用客户端的人都可以打开开发人员控制台并检查标题内容。它是对的吗?@marcellorvalle通常,使用API密钥保护的API的客户端是另一个服务。如果您推断此API的客户端将是一个web浏览器,那么我建议您研究OAuth/JWT令牌以获得用户授权。难道不是相反吗?主体应该是“N/A”、“API密钥持有者”或类似的,然后凭证将是实际的API密钥?这对我来说非常有效。但是,建议在生产中使用吗?一般来说,API密钥不如OAuth安全。但它们更简单,这也是吸引力的一部分。这一权衡是否值得,取决于您的需求和应用程序的部署方式。我的特定应用程序是一个内部应用程序,不接受来自外部世界的连接,因此在我的情况下,权衡是值得的。但是,例如,我不会将API密钥作为唯一的安全机制部署到移动应用程序,因为应用程序的任何用户都可以获得该API密钥。