|
|
@@ -143,14 +143,25 @@ public class ApnsPushClient {
|
|
|
}
|
|
|
}
|
|
|
if (hasTokenCredential(credential)) {
|
|
|
- ApnsSigningKey signingKey = ApnsSigningKey.loadFromInputStream(
|
|
|
- new ByteArrayInputStream(credential.getString("privateKey").getBytes(StandardCharsets.UTF_8)),
|
|
|
- credential.getString("teamId"),
|
|
|
- credential.getString("keyId"));
|
|
|
- return new ApnsClientBuilder()
|
|
|
- .setApnsServer(apnsHost)
|
|
|
- .setSigningKey(signingKey)
|
|
|
- .build();
|
|
|
+ String pem = normalizePrivateKeyPem(credential.getString("privateKey"));
|
|
|
+ if (StringUtils.isBlank(pem)) {
|
|
|
+ log.error("APNs privateKey 为空或格式无法识别");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ ApnsSigningKey signingKey = ApnsSigningKey.loadFromInputStream(
|
|
|
+ new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8)),
|
|
|
+ credential.getString("teamId"),
|
|
|
+ credential.getString("keyId"));
|
|
|
+ return new ApnsClientBuilder()
|
|
|
+ .setApnsServer(apnsHost)
|
|
|
+ .setSigningKey(signingKey)
|
|
|
+ .build();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("APNs privateKey 加载失败, teamId={}, keyId={}, err={}",
|
|
|
+ credential.getString("teamId"), credential.getString("keyId"), e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
log.error("APNs 凭证不完整,需配置 p12Base64+p12Password(证书方式)或 teamId+keyId+privateKey(Token 方式)");
|
|
|
return null;
|
|
|
@@ -223,6 +234,65 @@ public class ApnsPushClient {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 将 credential_json 中的 privateKey 规范为 PEM 多行格式,供 {@link ApnsSigningKey#loadFromInputStream} 使用。
|
|
|
+ * <p>支持:JSON 中 {@code \\n} 转义、整行无换行、标准多行 PEM。</p>
|
|
|
+ * <p>格式转换示例(单行 → 多行):</p>
|
|
|
+ * <pre>
|
|
|
+ * 转换前: -----BEGIN PRIVATE KEY-----MIGTAgEA...PCZ6m-----END PRIVATE KEY-----
|
|
|
+ * 转换后:
|
|
|
+ * -----BEGIN PRIVATE KEY-----
|
|
|
+ * MIGTAgEA...(每 64 字符换行)
|
|
|
+ * ...PCZ6m
|
|
|
+ * -----END PRIVATE KEY-----
|
|
|
+ * </pre>
|
|
|
+ */
|
|
|
+ private String normalizePrivateKeyPem(String rawKey) {
|
|
|
+ if (StringUtils.isBlank(rawKey)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String key = rawKey.trim()
|
|
|
+ .replace("\\r", "")
|
|
|
+ .replace("\\n", "\n");
|
|
|
+ if (key.contains("\n")) {
|
|
|
+ return key.endsWith("\n") ? key : key + "\n";
|
|
|
+ }
|
|
|
+ if (key.contains(PKCS8_BEGIN)) {
|
|
|
+ return reformatSingleLinePem(key, PKCS8_BEGIN, PKCS8_END);
|
|
|
+ }
|
|
|
+ if (key.contains(EC_PKCS8_BEGIN)) {
|
|
|
+ return reformatSingleLinePem(key, EC_PKCS8_BEGIN, EC_PKCS8_END);
|
|
|
+ }
|
|
|
+ log.warn("APNs privateKey 缺少 PEM 头尾标记 BEGIN/END PRIVATE KEY");
|
|
|
+ return key;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final String PKCS8_BEGIN = "-----BEGIN PRIVATE KEY-----";
|
|
|
+ private static final String PKCS8_END = "-----END PRIVATE KEY-----";
|
|
|
+ private static final String EC_PKCS8_BEGIN = "-----BEGIN EC PRIVATE KEY-----";
|
|
|
+ private static final String EC_PKCS8_END = "-----END EC PRIVATE KEY-----";
|
|
|
+
|
|
|
+ private String reformatSingleLinePem(String singleLine, String beginMarker, String endMarker) {
|
|
|
+ int beginIdx = singleLine.indexOf(beginMarker);
|
|
|
+ int endIdx = singleLine.indexOf(endMarker);
|
|
|
+ if (beginIdx < 0 || endIdx <= beginIdx) {
|
|
|
+ return singleLine;
|
|
|
+ }
|
|
|
+ String base64Body = singleLine.substring(beginIdx + beginMarker.length(), endIdx)
|
|
|
+ .replaceAll("\\s", "");
|
|
|
+ if (StringUtils.isBlank(base64Body)) {
|
|
|
+ log.warn("APNs privateKey PEM 主体为空");
|
|
|
+ return singleLine;
|
|
|
+ }
|
|
|
+ StringBuilder pem = new StringBuilder();
|
|
|
+ pem.append(beginMarker).append('\n');
|
|
|
+ for (int offset = 0; offset < base64Body.length(); offset += 64) {
|
|
|
+ pem.append(base64Body, offset, Math.min(offset + 64, base64Body.length())).append('\n');
|
|
|
+ }
|
|
|
+ pem.append(endMarker).append('\n');
|
|
|
+ return pem.toString();
|
|
|
+ }
|
|
|
+
|
|
|
private void closeClient(ApnsClient client) {
|
|
|
if (client == null) {
|
|
|
return;
|