Java中生成API Key并加密存储及接口鉴权的最优方案咨询
Java中API Key认证的最优实现方案
核心思路
- 生成高熵API Key,客户端留存明文,服务端仅存储不可逆的加密哈希值
- 基于Spring Security实现请求拦截,校验请求头
API_KEY与数据库哈希值的匹配性 - 不推荐JWT:JWT是自包含令牌,更适合会话场景;API Key的简单密钥校验场景下,哈希校验方案更轻量、安全
步骤1:安全生成API Key
使用SecureRandom生成高随机度的字节数组,再通过Base64编码转为易传输的字符串:
import java.security.SecureRandom; import java.util.Base64; public class ApiKeyGenerator { private static final int KEY_BYTES_LENGTH = 32; // 256位长度,满足安全要求 public static String generateApiKey() { SecureRandom secureRandom = new SecureRandom(); byte[] keyBytes = new byte[KEY_BYTES_LENGTH]; secureRandom.nextBytes(keyBytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes); } }
步骤2:加密存储API Key
采用BCrypt自适应哈希算法存储,防暴力破解,避免明文存储:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class ApiKeyEncoder { // 工作因子设为12,平衡安全性与性能 private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); public static String encodeApiKey(String rawApiKey) { return encoder.encode(rawApiKey); } public static boolean matches(String rawApiKey, String encodedApiKey) { return encoder.matches(rawApiKey, encodedApiKey); } }
步骤3:Spring Security拦截校验
自定义API Key过滤器
拦截所有API请求,提取请求头并校验:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; public class ApiKeyAuthFilter extends OncePerRequestFilter { private final ApiKeyService apiKeyService; public ApiKeyAuthFilter(ApiKeyService apiKeyService) { this.apiKeyService = apiKeyService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String apiKey = request.getHeader("API_KEY"); if (apiKey != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 从数据库获取对应客户端的加密API Key(实际业务可根据clientId/应用ID关联) String encodedApiKey = apiKeyService.getEncodedApiKey(request.getParameter("clientId")); if (encodedApiKey != null && ApiKeyEncoder.matches(apiKey, encodedApiKey)) { // 设置认证上下文,可扩展加入权限信息 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( request.getParameter("clientId"), null, null // 如需权限控制,传入GrantedAuthority列表 ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的API Key"); return; } } filterChain.doFilter(request, response); } }
Spring Security配置
将自定义过滤器加入拦截链:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final ApiKeyService apiKeyService; public SecurityConfig(ApiKeyService apiKeyService) { this.apiKeyService = apiKeyService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // API场景默认关闭CSRF .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .addFilterBefore(new ApiKeyAuthFilter(apiKeyService), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
模拟API Key数据服务
替换为实际数据库查询逻辑:
import org.springframework.stereotype.Service; @Service public class ApiKeyService { // 实际项目中用JPA/MyBatis实现数据库查询 public String getEncodedApiKey(String clientId) { if ("client_001".equals(clientId)) { // 模拟从数据库取出的BCrypt加密值 return "$2a$12$ZkDd5WzX7yF9hG8jK6lM.NvUoIpQrStVbWcXdYeRfTgShUiJkLmNo"; } return null; } }
关键注意事项
- 禁止明文存储:BCrypt的自适应哈希特性,即使数据库泄露,攻击者也难以暴力破解
- 密钥分发:生成后仅向客户端展示一次,客户端需自行妥善保存,丢失后需重新生成
- 权限扩展:可在认证时为客户端绑定不同权限,实现细粒度API访问控制
- 定期轮换:提供API Key轮换机制,降低密钥泄露后的风险
内容的提问来源于stack exchange,提问作者Girija Dash
相关产品推荐
相关产品推荐

