使用 API Key 和 Secret 保护 Spring Boot API
最后更新:2025 年 3 月 26 日
1. 概述
安全性在 REST API 开发中起着至关重要的作用。 不安全的 REST API 可以直接访问后端系统中的敏感数据。 因此,组织需要关注 API 安全性。Spring Security 提供了各种机制来保护我们的 REST API。 其中之一是 API 密钥。 API 密钥是客户端在调用 API 时提供的令牌。 在本教程中,我们将讨论在 Spring Security 中实现基于 API 密钥的身份验证。
2. REST API 安全性
Spring Security 可用于保护 REST API。REST API 是无状态的。 因此,它们不应使用会话或 cookie。相反,应该使用 基本身份验证、API 密钥、JWT 或 OAuth2-based 令牌来保护它们。
2.1. 基本身份验证
基本身份验证是一种简单的身份验证方案。 客户端使用包含单词 Basic 后跟空格和 Base64 编码字符串 username:password 的 HTTP Authorization 标头发送 HTTP 请求。 基本身份验证只有与其他安全机制(如 HTTPS/SSL)结合使用时才被认为安全。
2.2. OAuth2
OAuth2 是 REST API 安全性的事实标准。 这是一个开放的身份验证和授权标准,允许资源所有者通过访问令牌向客户端授予对私有数据的委托访问权限。
2.3. API 密钥
一些 REST API 使用 API 密钥进行身份验证。 API 密钥是一个令牌,它在不引用实际用户的情况下将 API 客户端标识给 API。 令牌可以发送在查询字符串中或作为请求标头。 与基本身份验证一样,可以使用 SSL 隐藏密钥。 在本教程中,我们重点介绍使用 Spring Security 实现 API 密钥身份验证。
3. 使用 API 密钥保护 REST API
在本节中,我们将创建一个 Spring Boot 应用程序并使用基于 API 密钥的身份验证来保护它。
3.1. Maven 依赖
让我们首先在我们的 pom.xml 中声明 spring-boot-starter-security 依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.2. 创建自定义过滤器
思路是从请求中获取 HTTP API Key 标头,然后将密钥与我们的配置进行检查。在这种情况下,我们需要在 Spring Security 配置类中添加一个 自定义 Filter 。 我们将从实现 GenericFilterBean 开始。GenericFilterBean 是一个简单的 javax.servlet.Filter 实现,它感知 Spring。 让我们创建 AuthenticationFilter 类
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
try {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (Exception exp) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = httpResponse.getWriter();
writer.print(exp.getMessage());
writer.flush();
writer.close();
}
}
}
我们只需要实现一个 doFilter() 方法。 我们在这个方法中评估 API Key 标头,并将结果 Authentication 对象设置到当前的 SecurityContext 实例中。 然后,请求传递到剩余的过滤器进行处理,路由到 DispatcherServlet,最后到我们的控制器。 如果出现问题,我们会捕获 Exception 并写回给调用者,而不会继续执行过滤器链。 我们将 API Key 的评估和构造 Authentication 对象委托给 AuthenticationService 类
public class AuthenticationService {
private static final String AUTH_TOKEN_HEADER_NAME = "X-API-KEY";
private static final String AUTH_TOKEN = "Baeldung";
public static Authentication getAuthentication(HttpServletRequest request) {
String apiKey = request.getHeader(AUTH_TOKEN_HEADER_NAME);
if (apiKey == null || !apiKey.equals(AUTH_TOKEN)) {
throw new BadCredentialsException("Invalid API Key");
}
return new ApiKeyAuthentication(apiKey, AuthorityUtils.NO_AUTHORITIES);
}
}
在这里,我们检查请求是否包含带有密钥的 API Key 头。如果该头为null或不等于密钥,我们会抛出一个BadCredentialsException。如果请求包含该头,它将执行身份验证,将密钥添加到安全上下文中,然后将调用传递给下一个安全过滤器。我们的getAuthentication方法非常简单——我们比较 API Key 头和密钥与静态值。为了构造Authentication对象,我们必须使用 Spring Security 通常用于使用标准身份验证构建对象的方法。因此,让我们扩展AbstractAuthenticationToken类并手动触发身份验证。
3.3. 扩展AbstractAuthenticationToken
为了成功实现应用程序的身份验证,我们需要将传入的 API Key 转换为Authentication对象,例如AbstractAuthenticationToken。AbstractAuthenticationToken类实现了Authentication接口,代表经过身份验证的请求的密钥/主体。让我们创建ApiKeyAuthentication类
public class ApiKeyAuthentication extends AbstractAuthenticationToken {
private final String apiKey;
public ApiKeyAuthentication(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;
}
}
ApiKeyAuthentication类是一种AbstractAuthenticationToken对象,包含从 HTTP 请求获取的apiKey信息。我们在构造函数中使用setAuthenticated(true)方法。因此,Authentication对象包含apiKey和authenticated字段
3.4. 安全配置
我们可以通过创建SecurityFilterChain bean 以编程方式注册自定义过滤器。在这种情况下,我们需要使用HttpSecurity实例上的addFilterBefore()方法在UsernamePasswordAuthenticationFilter类之前添加AuthenticationFilter。让我们创建SecurityConfig类
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry.requestMatchers("/**").authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
此外,会话策略设置为STATELESS,因为我们将使用 REST 端点。
3.5. ResourceController
最后,我们将创建一个具有/home映射的ResourceController
@RestController
public class ResourceController {
@GetMapping("/home")
public String homeEndpoint() {
return "Baeldung !";
}
}
3.6. 禁用默认自动配置
我们需要丢弃安全自动配置。为此,我们排除SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration类
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class})
public class ApiKeySecretAuthApplication {
public static void main(String[] args) {
SpringApplication.run(ApiKeySecretAuthApplication.class, args);
}
}
现在,应用程序已准备好进行测试。
4. 测试
我们可以使用 curl 命令来消费受保护的应用程序。首先,让我们尝试在不提供任何安全凭据的情况下请求/home
curl --location --request GET 'https://:8080/home'
我们得到了预期的401 Unauthorized。现在让我们请求相同的资源,但提供 API Key 和密钥来访问它
curl --location --request GET 'https://:8080/home' \
--header 'X-API-KEY: Baeldung'
结果,服务器的响应是200 OK。
5. 结论
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。
















