首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Servlet 手機 App 介面安全系列 (一):MD5 簽名驗證、Token 雙層設計與防篡改實戰

     編輯:Java 相關知識

手機 App 介面公開於公網,面臨參數竄改、重放攻擊、中間人攻擊等風險。本文實戰一套雙 Token + MD5 簽名防篡改體系:idToken 識別用戶、encryptToken 參與簽名但不傳輸、請求參數排序拼接 → MD5 → 與客戶端簽名比對,附完整 Servlet 代碼與攻擊場景分析。

威脅模型與設計目標

威脅對策
參數竄改 (金額、用戶 ID)簽名覆蓋所有業務參數,篡改即驗證失敗
重放攻擊引入 timestamp/nonce + 服務端快取去重 (本文簡化未示範)
中間人竊取密鑰encryptToken 僅存服務端、登入時下發、不隨請求傳輸
簽名算法逆向MD5 不可逆,但建議升級 HMAC-SHA256 + 密鑰輪換

核心流程

  1. 註冊/登入:服務端生成 idToken (用戶唯一標識) + encryptToken (簽名密鑰),雙雙存資料庫,idToken 回傳客戶端,encryptToken 僅回傳一次 (登入響應體)。
  2. 客戶端請求:組裝業務 JSON → 加入 idToken → 拼接 encryptToken → MD5(UTF-8) → md5Str → 發送 {parameter: {...}, md5Str: "..."}。
  3. 服務端驗證:取 idToken 查 DB 取 encryptToken → 同樣拼接業務 JSON + encryptToken → MD5 → 比對 md5Str。

關鍵代碼實現

1. MD5 工具類 (32 位小寫)

public class Md5Util {
  public static String encrypt(String src) {
    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] bytes = md.digest(src.getBytes(StandardCharsets.UTF_8));
      StringBuilder sb = new StringBuilder();
      for (byte b : bytes) {
        sb.append(String.format("%02x", b));
      }
      return sb.toString();
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
    }
  }
}

2. 服務端驗證核心邏輯

public class SignVerifier {
  public static int verify(String idToken, JSONObject bizParams, String clientSign) {
    // 1. 查詢 encryptToken
    String sql = "SELECT encrypt_token FROM user_tokens WHERE id_token=?";
    String encryptToken = JdbcTemplate.queryForObject(sql, String.class, idToken);
    if (encryptToken == null) return 1; // idToken 無效

    // 2. 重構簽名字串:業務參數 JSON (按 key 排序) + encryptToken
    //    這裡簡化:直接用 JSONObject.toString() (無序),生產必須按 key 排序
    String signSrc = bizParams.toString() + encryptToken; 
    String serverSign = Md5Util.encrypt(signSrc);

    // 3. 比對
    return serverSign.equals(clientSign) ? 0 : 2; // 0=成功, 2=簽名不符
  }
}

3. Servlet 入口

@WebServlet("/api/*")
public class ApiGatewayServlet extends HttpServlet {
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    req.setCharacterEncoding("UTF-8");
    resp.setContentType("application/json;charset=UTF-8");

    // 解決中文亂碼 (Tomcat 8+ 需 URIEncoding="UTF-8")
    String paramJson = new String(req.getParameter("parameter").getBytes("ISO-8859-1"), "UTF-8");
    
    JSONObject wrapper = JSONObject.parseObject(paramJson);
    JSONObject bizParams = JSONObject.parseObject(wrapper.getString("parameter"));
    String clientSign = wrapper.getString("md5Str");
    String idToken = bizParams.getString("idToken");

    int code = SignVerifier.verify(idToken, bizParams, clientSign);
    if (code != 0) {
      resp.getWriter().write("{\"code\":" + code + ",\"msg\":\"簽名驗證失敗\"}");
      return;
    }

    // 簽名通過,分發具體業務 Handler
    String action = req.getPathInfo(); // /login, /order/create ...
    dispatch(action, bizParams, resp);
  }
}

4. 客戶端簽名生成 (Android/Java 同邏輯)

public String generateSign(JSONObject bizParams, String encryptToken) {
  // 1. 參數按 key 字典序排序拼接 key=value&key=value...
  // 2. 拼接 encryptToken
  // 3. MD5
  String sorted = bizParams.keySet().stream()
      .sorted()
      .map(k -> k + "=" + bizParams.getString(k))
      .collect(Collectors.joining("&"));
  return Md5Util.encrypt(sorted + encryptToken);
}

常見坑點與加固建議

  1. 參數排序必須雙端一致:服務端 JSONObject.toString() 無序,生產必須按 key 排序拼接 (如 key1=val1&key2=val2),否則同參數不同序導致簽名不一致。
  2. encryptToken 僅登入時下發一次:後續請求攜帶 idToken,服務端查 DB 取 encryptToken 參與驗證,網路層永不傳輸。
  3. MD5 碰撞風險:建議升級 HmacSHA256 + SecretKeySpec,密鑰定期輪換。
  4. 時間戳/Nonce 防重放:業務參數加入 timestamp (允許 ±5 分鐘) + nonce (Redis Set 去重,TTL 10 分鐘)。
  5. HTTPS 強制:雖有簽名,但明文傳輸業務參數仍可被竊取語義信息,全站強制 TLS 1.2+。
  6. 登入介面同樣需簽名:登入請求用 idToken="LOGIN" 固定值 + encryptToken 從註冊時返回,防止登入介面被暴力破解。

總結

雙 Token + MD5 簽名以「密鑰不出服務端、參數全覆蓋、不可逆摘要」三原則,構建輕量級介面防篡改體系。掌握參數排序一致性、密鑰生命週期管理、重放防護、算法升級路徑四大工程細節,即可從「能跑通」進化到「經得起攻擊」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved