代码实战:接口安全之API Key + Secret认证机制

打印 上一主题 下一主题

主题 1788|帖子 1788|积分 5366

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
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. 具体实现

  1. public class ApiAuthorizationUtils {
  2.     /**
  3.      * 客户端生成签名.
  4.      *
  5.      * @param appId     标记唯一第三方应用
  6.      * @param secret    密钥
  7.      * @param timestamp 时间戳
  8.      * @param nonce     随机数
  9.      * @return 验签
  10.      */
  11.     public static String generateSignature(String appId, String secret, Long timestamp, String nonce) {
  12.         try {
  13.             // 构造待签名的字符串
  14.             Map<String, String> params = new TreeMap<>();
  15.             params.put("appid", appId);
  16.             params.put("timestamp", String.valueOf(timestamp));
  17.             params.put("nonce", nonce);
  18.             StringBuilder sb = new StringBuilder();
  19.             for (Map.Entry<String, String> entry : params.entrySet()) {
  20.                 sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
  21.             }
  22.             String signString = sb.substring(0, sb.length() - 1);
  23.             // 使用HMAC-SHA256进行签名
  24.             Mac sha256Hmac = Mac.getInstance("HmacSHA256");
  25.             SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
  26.             sha256Hmac.init(secretKey);
  27.             byte[] hash = sha256Hmac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
  28.             return Base64.getEncoder().encodeToString(hash);
  29.         } catch (Exception e) {
  30.             throw new RuntimeException("Failed to generate signature", e);
  31.         }
  32.     }
  33.     /**
  34.      * 验证客户端签名.
  35.      *
  36.      * @param appId           appId
  37.      * @param secret          secret
  38.      * @param timestamp       时间戳
  39.      * @param nonce           随机数
  40.      * @param clientSignature 客户端验签
  41.      * @return 是否验证成功
  42.      */
  43.     public static Boolean verifySignature(String appId, String secret, Long timestamp, String nonce, String clientSignature) {
  44.         try {
  45.             // 构造待签名的字符串
  46.             Map<String, String> params = new TreeMap<>();
  47.             params.put("appid", appId);
  48.             params.put("timestamp", String.valueOf(timestamp));
  49.             params.put("nonce", nonce);
  50.             StringBuilder sb = new StringBuilder();
  51.             for (Map.Entry<String, String> entry : params.entrySet()) {
  52.                 sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
  53.             }
  54.             String signString = sb.substring(0, sb.length() - 1);
  55.             // 使用HMAC-SHA256进行签名
  56.             Mac sha256Hmac = Mac.getInstance("HmacSHA256");
  57.             SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
  58.             sha256Hmac.init(secretKey);
  59.             byte[] hash = sha256Hmac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
  60.             String serverSignature = Base64.getEncoder().encodeToString(hash);
  61.             return serverSignature.equals(clientSignature);
  62.         } catch (Exception e) {
  63.             throw new RuntimeException("Failed to verify signature", e);
  64.         }
  65.     }
  66.     /**
  67.      * 验证时间有效性(5分钟内有效).
  68.      *
  69.      * @param timestamp timestamp
  70.      * @return 是否有效
  71.      */
  72.     public static boolean isTimestampValid(long timestamp) {
  73.         long currentTime = System.currentTimeMillis();
  74.         long timeDiff = Math.abs(currentTime - timestamp);
  75.         return timeDiff <= 5 * 60 * 1000;
  76.     }
  77. }
复制代码
  1. Configuration
  2. @Slf4j
  3. public class AuthInterceptor implements WebMvcConfigurer {
  4.     /**
  5.      * 密钥,一般加密后存在数据库中,或者配置在配置文件.
  6.      */
  7.     private static final String SECRET = "123456";
  8.     @Override
  9.     public void addInterceptors(InterceptorRegistry registry) {
  10.         registry.addInterceptor(new HandlerInterceptor() {
  11.             @Override
  12.             public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  13.                 PrintWriter writer = response.getWriter();
  14.                 // 从请求头中获取值
  15.                 String appid = request.getHeader("appId");
  16.                 Long timestamp = Long.valueOf(request.getHeader("timestamp"));
  17.                 String nonce = request.getHeader("nonce");
  18.                 String signature = request.getHeader("signature");
  19.                 if (StrUtil.isBlank(appid) || StrUtil.isBlank(nonce) || StrUtil.isBlank(signature)) {
  20.                     writer.append("permission denied, params is not valid");
  21.                     return false;
  22.                 }
  23.                 if (!ApiAuthorizationUtils.isTimestampValid(timestamp)) {
  24.                     writer.append("签名已过期");
  25.                     return false;
  26.                 }
  27.                 // 服务端验证签名
  28.                 boolean isValid = ApiAuthorizationUtils.verifySignature(appid, SECRET, timestamp, nonce, signature);
  29.                 if (isValid) {
  30.                     return true;
  31.                 } else {
  32.                     writer.append("权限认证失败");
  33.                     return false;
  34.                 }
  35.             }
  36.         }).addPathPatterns("/api/noauth/external/**"); // 第三方路由
  37.     }
  38. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81428

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表