Vit*_*siv 28 java api spring spring-security spring-boot
我想保护Spring Boot API,以便只有具有有效API密钥和密钥的客户端才能访问它.但是,程序内部没有身份验证(使用用户名和密码进行标准登录),因为所有数据都是匿名的.我想要实现的是,所有API请求只能用于特定的第三方前端.
我发现了很多关于如何使用用户身份验证保护Spring Boot API的文章.但我不需要用户身份验证.我在想的只是为我的客户提供API密钥和秘密,以便他可以访问端点.
你能告诉我怎样才能做到这一点?谢谢!
Mar*_*all 47
创建一个过滤器,用于获取您用于身份验证的标头.
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";
}
}
Run Code Online (Sandbox Code Playgroud)
在Web Security配置中配置过滤器.
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();
}
}
Run Code Online (Sandbox Code Playgroud)
小智 13
以 @MarkOfHall 的答案为基础,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();
}
}
Run Code Online (Sandbox Code Playgroud)
我意识到我在这个游戏上有点晚了,但我也设法让 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;
}
}
Run Code Online (Sandbox Code Playgroud)
还有过滤器
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;
}
}
Run Code Online (Sandbox Code Playgroud)
此时剩下的就是在链中的适当位置注入过滤器。就我而言,我希望在任何用户名/密码身份验证之前评估 API 密钥身份验证,以便它可以在应用程序尝试重定向到登录页面之前对请求进行身份验证:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterBefore(new ApiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin();
}
Run Code Online (Sandbox Code Playgroud)
我要说的另一件事是您应该注意的是,您的 API 密钥身份验证请求不会HttpSession在您的服务器上创建和丢弃一堆s。
@MarkOfHall 的答案是正确的,我只想添加更多细节。获得代码后,您需要将属性值添加到文件中,application.properties如下所示:
yourapp.http.auth-token-header-name=X-API-KEY
yourapp.http.auth-token=abc123
Run Code Online (Sandbox Code Playgroud)
在Postman中设置认证值如下:
您可以使用 Postman,但如果您使用cURL请求,将提供类似以下内容:
$ curl -H "X-API-KEY: abc123" "http://localhost:8080/api/v1/property/1"
Run Code Online (Sandbox Code Playgroud)
除非提供正确的键和值,否则应用程序将无法运行。
基于 @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();
}
}
Run Code Online (Sandbox Code Playgroud)
application.properties中所需配置:
app.http.auth-token-header-name=X-API-Key
app.http.auth-token=109353c6-6432-4acf-8e77-ef842f64a664
Run Code Online (Sandbox Code Playgroud)
pom.xml中的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 Postman,请单击集合并编辑“授权”选项卡:

| 归档时间: |
|
| 查看次数: |
22390 次 |
| 最近记录: |