我想保护Spring Boot API,使其只能被拥有有效API密钥和密钥的客户端访问。但是程序内部没有身份验证(使用用户名和密码的标准登录)因为所有数据都是匿名的。我想要实现的只是所有API请求仅能用于特定的第三方前端。
我找到了很多关于如何通过用户身份验证来保护Spring Boot API的文章。但我不需要用户身份验证。我所考虑的是只提供客户端API密钥和密钥,以便他可以访问终点。
请问您能否建议我如何实现这一点?谢谢!
我想保护Spring Boot API,使其只能被拥有有效API密钥和密钥的客户端访问。但是程序内部没有身份验证(使用用户名和密码的标准登录)因为所有数据都是匿名的。我想要实现的只是所有API请求仅能用于特定的第三方前端。
我找到了很多关于如何通过用户身份验证来保护Spring Boot 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();
}
}
我意识到在这件事上我有些晚,但我还是成功地让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
。
WebSecurityConfigurerAdapter
已被弃用(请参见https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)。因此,他的APISecurityConfig
版本现在将如下所示:package com.fasset.ledger.auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@Order(1)
public class APISecurityConfig {
@Value("${yourapp.http.auth-token-header-name}")
private String principalRequestHeader;
@Value("${yourapp.http.auth-token}")
private String principalRequestValue;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) 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;
}
});
http.antMatcher("/api/**").
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
return http.build();
}
}
@MarkOfHall的回答是正确的,我只想再添加一些细节。在您拥有代码之后,您需要将属性值添加到application.properties
文件中,如下所示:
yourapp.http.auth-token-header-name=X-API-KEY
yourapp.http.auth-token=abc123
$ curl -H "X-API-KEY: abc123" "http://localhost:8080/api/v1/property/1"
在 @zawar 和 @MarkOfHall 的回答以及来自 https://github.com/gregwhitaker/springboot-apikey-example 的资料基础上,截至2022年12月8日,一种现代的解决方案如下:
package com.mygloriousapp.auth;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
/**
* Filter responsible for getting the api key off of incoming requests that need to be authorized.
*/
public class ApiKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private final String headerName;
public ApiKeyAuthFilter(final String headerName) {
this.headerName = headerName;
}
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(headerName);
}
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// No credentials when using API key
return null;
}
}
package com.mygloriousapp.config;
import com.mygloriousapp.auth.ApiKeyAuthFilter;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig {
@Value("${app.http.auth-token-header-name}")
private String principalRequestHeader;
@Value("${app.http.auth-token}")
private String principalRequestValue;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
ApiKeyAuthFilter filter = new ApiKeyAuthFilter(principalRequestHeader);
filter.setAuthenticationManager(
authentication -> {
String principal = (String) authentication.getPrincipal();
if (!Objects.equals(principalRequestValue, principal)) {
throw new BadCredentialsException(
"The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
});
http.antMatcher("/**")
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(filter)
.authorizeRequests()
.anyRequest()
.authenticated();
return http.build();
}
}
在 application.properties 中需要进行必要的配置:
app.http.auth-token-header-name=X-API-Key
app.http.auth-token=109353c6-6432-4acf-8e77-ef842f64a664
在pom.xml中的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
import jakarta.servlet.http.HttpServletRequest;
。 - M Smith