> 技术文档 > 代码实战:接口安全之API Key + Secret认证机制_java对外接口鉴权key secret

代码实战:接口安全之API Key + Secret认证机制_java对外接口鉴权key secret


文章目录

  • 1.实战背景
  • 2.核心目标
  • 3.实现方案 API Key + Secret认证机制
  • 4. 具体实现

1.实战背景

如何保证服务端和客户端的HTTP接口安全?
即对外提供部分特定接口(比如:/**/noauth),第三方调用这些接口既不能走我们自己的权限认证(比如:header带着登录后的token),也不能随意让任何人随意调用。如何保证提供给第三方接口的安全性,特别如何校验第三方的身份?

2.核心目标

身份验证 - 确保调用方是合法的第三方。
数据完整性 - 防止请求数据被篡改。
防重放攻击 - 防止请求被恶意重复使用。
访问控制 - 限制第三方只能访问特定接口。

3.实现方案 API Key + Secret认证机制

step1:生成签名
客户端使用appid、secret、时间戳(Timestamp)和随机字符串(Nonce)生成签名。签名算法使用HMAC-SHA256。【时间戳个随机数防重放攻击】
step2:发送请求
客户端将appId、Timestamp、Nonce和签名作为请求头发送到服务端。
step3:验证签名
服务端根据appId查找对应的secret,使用相同的算法对请求参数进行签名,并与客户端提供的签名进行比对。
重点:要对外提供appid、secret,以及签名生成算法
// 其他方案也可,包括HTTPS加密传输、IP白名单等

4. 具体实现

public class ApiAuthorizationUtils { /** * 客户端生成签名. * * @param appId 标记唯一第三方应用 * @param secret 密钥 * @param timestamp 时间戳 * @param nonce 随机数 * @return 验签 */ public static String generateSignature(String appId, String secret, Long timestamp, String nonce) { try { // 构造待签名的字符串 Map<String, String> params = new TreeMap<>(); params.put(\"appid\", appId); params.put(\"timestamp\", String.valueOf(timestamp)); params.put(\"nonce\", nonce); StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"&\"); } String signString = sb.substring(0, sb.length() - 1); // 使用HMAC-SHA256进行签名 Mac sha256Hmac = Mac.getInstance(\"HmacSHA256\"); SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"); sha256Hmac.init(secretKey); byte[] hash = sha256Hmac.doFinal(signString.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(hash); } catch (Exception e) { throw new RuntimeException(\"Failed to generate signature\", e); } } /** * 验证客户端签名. * * @param appId  appId * @param secret secret * @param timestamp 时间戳 * @param nonce  随机数 * @param clientSignature 客户端验签 * @return 是否验证成功 */ public static Boolean verifySignature(String appId, String secret, Long timestamp, String nonce, String clientSignature) { try { // 构造待签名的字符串 Map<String, String> params = new TreeMap<>(); params.put(\"appid\", appId); params.put(\"timestamp\", String.valueOf(timestamp)); params.put(\"nonce\", nonce); StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { sb.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"&\"); } String signString = sb.substring(0, sb.length() - 1); // 使用HMAC-SHA256进行签名 Mac sha256Hmac = Mac.getInstance(\"HmacSHA256\"); SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"); sha256Hmac.init(secretKey); byte[] hash = sha256Hmac.doFinal(signString.getBytes(StandardCharsets.UTF_8)); String serverSignature = Base64.getEncoder().encodeToString(hash); return serverSignature.equals(clientSignature); } catch (Exception e) { throw new RuntimeException(\"Failed to verify signature\", e); } } /** * 验证时间有效性(5分钟内有效). * * @param timestamp timestamp * @return 是否有效 */ public static boolean isTimestampValid(long timestamp) { long currentTime = System.currentTimeMillis(); long timeDiff = Math.abs(currentTime - timestamp); return timeDiff <= 5 * 60 * 1000; }}
Configuration@Slf4jpublic class AuthInterceptor implements WebMvcConfigurer { /** * 密钥,一般加密后存在数据库中,或者配置在配置文件. */ private static final String SECRET = \"123456\"; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { PrintWriter writer = response.getWriter(); // 从请求头中获取值 String appid = request.getHeader(\"appId\"); Long timestamp = Long.valueOf(request.getHeader(\"timestamp\")); String nonce = request.getHeader(\"nonce\"); String signature = request.getHeader(\"signature\"); if (StrUtil.isBlank(appid) || StrUtil.isBlank(nonce) || StrUtil.isBlank(signature)) {  writer.append(\"permission denied, params is not valid\");  return false; } if (!ApiAuthorizationUtils.isTimestampValid(timestamp)) {  writer.append(\"签名已过期\");  return false; } // 服务端验证签名 boolean isValid = ApiAuthorizationUtils.verifySignature(appid, SECRET, timestamp, nonce, signature); if (isValid) {  return true; } else {  writer.append(\"权限认证失败\");  return false; } } }).addPathPatterns(\"/api/noauth/external/**\"); // 第三方路由 }}