diff --git a/appbase/build.properties b/appbase/build.properties index 12a209d..2e5a1a5 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Jan 21 03:11:44 GMT 2026 +#Thu Jan 22 12:38:42 GMT 2026 stageCount=7 libraryProject=libappbase baseVersion=15.15 publishVersion=15.15.6 -buildCount=1 +buildCount=11 baseBetaVersion=15.15.7 diff --git a/libappbase/build.gradle b/libappbase/build.gradle index b11c04e..f5f89d2 100644 --- a/libappbase/build.gradle +++ b/libappbase/build.gradle @@ -21,5 +21,11 @@ android { } dependencies { + // 网络连接类库 + api 'com.squareup.okhttp3:okhttp:4.4.1' + // Gson + api 'com.google.code.gson:gson:2.8.9' + // Html 解析 + api 'org.jsoup:jsoup:1.13.1' api fileTree(dir: 'libs', include: ['*.jar']) } diff --git a/libappbase/build.properties b/libappbase/build.properties index 12a209d..2e5a1a5 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Jan 21 03:11:44 GMT 2026 +#Thu Jan 22 12:38:42 GMT 2026 stageCount=7 libraryProject=libappbase baseVersion=15.15 publishVersion=15.15.6 -buildCount=1 +buildCount=11 baseBetaVersion=15.15.7 diff --git a/libappbase/src/main/AndroidManifest.xml b/libappbase/src/main/AndroidManifest.xml index 434c960..3ad3f91 100644 --- a/libappbase/src/main/AndroidManifest.xml +++ b/libappbase/src/main/AndroidManifest.xml @@ -3,7 +3,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="cc.winboll.studio.libappbase"> - + + + + + * @Date 2026/01/22 20:37 + */ +// ==================== JSON响应模型(与后端返回字段完全匹配)==================== +public class SignCheckResponse { + private int code; // 根节点code(后端返回) + private String msg; // 根节点提示信息(后端返回,替换原message) + private DataBean data; // 根节点data对象(后端返回) + + // 内部DataBean:对应后端返回的data字段内容 + public static class DataBean { + private boolean valid; // 实际是否合法的标识(后端data.valid) + private String signature; // 加密后的签名 + private String decryptedSign;// 解密后的原始签名 + private long validTime; // 时间戳 + } + + // Getter/Setter(关键:获取data中的valid字段) + public boolean isValid() { + return data != null && data.valid; // 从data中获取valid值 + } + + public String getMessage() { + return msg; // 对应后端根节点的msg字段 + } + + // 其他必要的Getter/Setter(用于后续扩展) + public int getCode() { + return code; + } + + public DataBean getData() { + return data; + } +} + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/APPUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/APPUtils.java index 306f78a..b4f3320 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/APPUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/APPUtils.java @@ -5,52 +5,137 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.util.Base64; +import cc.winboll.studio.libappbase.GlobalApplication; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.models.SignCheckResponse; +import com.google.gson.Gson; +import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Date; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; /** * @Author 豆包&ZhanGSKen * @Date 2026/01/20 19:17 - * @Describe APPUtils 应用包名、签名校验工具类 + * @Describe APPUtils 应用包名、签名校验工具类(OKHTTP网络校验版) */ public class APPUtils { - public static final String TAG = "APPUtils"; - // 目标应用签名指纹(BASE64格式,自行替换为你的合法指纹) - //public static final String TARGET_SIGN_FINGERPRINT = "你的应用签名SHA1指纹BASE64值"; - public static final String TARGET_SIGN_FINGERPRINT = "bMArVdXE4ZZo42vS9e/kXE63MkE="; - // 目标应用包名(自行替换为你的应用包名) - private static final String TARGET_PACKAGE_NAME = "cc.winboll.studio.你的应用包名"; + // 网络校验接口地址 + private static final String CHECK_API_URL = "https://console.winboll.cc/api/app-signatures-check"; + private static final String CHECK_API_URL_DEGUG = "http://localhost:8080/api/app-signatures-check"; + // OKHTTP客户端(单例复用) + private static OkHttpClient sOkHttpClient = new OkHttpClient(); + // Gson解析实例 + private static Gson sGson = new Gson(); /** - * 检查应用包名+签名指纹合法性,不匹配打日志,匹配无操作 + * 检查应用合法性(包名校验+OKHTTP网络校验签名) * @param context 上下文 + * @param callback 校验结果回调(主线程回调) */ - public static void checkAppValid(Context context) { + public static void checkAppValid(Context context, final CheckResultCallback callback) { if (context == null) { LogUtils.w(TAG, "checkAppValid: context为空,跳过校验"); + if (callback != null) callback.onResult(false, "context为空"); return; } - // 1. 校验包名 - String currentPkg = context.getPackageName(); - LogUtils.d(TAG, "checkAppValid: 当前应用包名=" + currentPkg + ",目标包名=" + TARGET_PACKAGE_NAME); - if (!TARGET_PACKAGE_NAME.equals(currentPkg)) { - LogUtils.e(TAG, "checkAppValid: 应用包名不匹配,非法环境"); - return; - } - // 2. 校验签名指纹 + + // 2. 获取当前应用签名(SHA1+Base64)和证书生效时间 String currentSign = getAppSignFingerprint(context); - LogUtils.d(TAG, "checkAppValid: 当前应用签名指纹=" + currentSign + ",目标指纹=" + TARGET_SIGN_FINGERPRINT); - if (currentSign == null || !TARGET_SIGN_FINGERPRINT.equals(currentSign)) { - LogUtils.e(TAG, "checkAppValid: 应用签名指纹不匹配,非法环境"); + long certValidTime = getCertValidTime(context); // 证书生效时间(毫秒时间戳) + if (currentSign == null) { + String errorMsg = "获取应用签名失败"; + LogUtils.e(TAG, "checkAppValid: " + errorMsg); + if (callback != null) callback.onResult(false, errorMsg); + return; + } + + // 新增:对currentSign进行Base64二次加密(URL安全编码,避免特殊字符) + String encryptedSign = base64Encode(currentSign); + LogUtils.d(TAG, "checkAppValid: 原始签名=" + currentSign + ",Base64二次加密后=" + encryptedSign); + + // 3. 构建请求URL(拼接加密后的签名参数) + String requestUrl = String.format("%s?signature=%s&validTime=%d", + GlobalApplication.isDebugging()?CHECK_API_URL_DEGUG:CHECK_API_URL, + encryptedSign, // 替换为加密后的签名 + certValidTime); + LogUtils.d(TAG, "checkAppValid: 发起网络校验请求,URL=" + requestUrl); + + // 4. OKHTTP发起异步GET请求 + Request request = new Request.Builder().url(requestUrl).build(); + sOkHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + final String errorMsg = "网络校验请求失败:" + e.getMessage(); + LogUtils.e(TAG, "checkAppValid: " + errorMsg, e); + if (callback != null) { + // 切换到主线程回调 + new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + callback.onResult(false, errorMsg); + } + }); + } + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful() && response.body() != null) { + String responseJson = response.body().string(); + LogUtils.d(TAG, "checkAppValid: 网络校验响应JSON=" + responseJson); + // 解析JSON响应 + SignCheckResponse checkResponse = sGson.fromJson(responseJson, SignCheckResponse.class); + final boolean isValid = checkResponse != null && checkResponse.isValid(); + final String msg = checkResponse != null ? checkResponse.getMessage() : "响应解析失败"; + if (callback != null) { + new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + callback.onResult(isValid, msg); + } + }); + } + } else { + final String errorMsg = "网络校验响应失败,code=" + response.code(); + LogUtils.e(TAG, "checkAppValid: " + errorMsg); + if (callback != null) { + new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + callback.onResult(false, errorMsg); + } + }); + } + } + } + }); + } + + /** + * 新增:Base64加密工具(URL安全编码,避免特殊字符影响URL拼接) + * @param content 待加密内容 + * @return 加密后的Base64字符串 + */ + private static String base64Encode(String content) { + try { + // 使用URL安全的Base64编码(替换+为-,/为_,去除=) + byte[] contentBytes = content.getBytes("UTF-8"); + return Base64.encodeToString(contentBytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } catch (Exception e) { + LogUtils.e(TAG, "base64Encode: 加密失败", e); + return content; // 加密失败则返回原始内容,避免请求异常 } } /** - * 获取当前应用签名SHA1指纹(BASE64编码,适配Java7) - * @param context 上下文 - * @return 签名指纹字符串,失败返回null + * 获取当前应用签名SHA1指纹(BASE64编码) */ private static String getAppSignFingerprint(Context context) { try { @@ -61,18 +146,49 @@ public class APPUtils { LogUtils.w(TAG, "getAppSignFingerprint: 未获取到应用签名"); return null; } - // SHA1摘要 + BASE64编码,和目标指纹格式统一 MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(signatures[0].toByteArray()); return Base64.encodeToString(md.digest(), Base64.NO_WRAP); - } catch (PackageManager.NameNotFoundException e) { - LogUtils.e(TAG, "getAppSignFingerprint: 包名未找到", e); - } catch (NoSuchAlgorithmException e) { - LogUtils.e(TAG, "getAppSignFingerprint: 不支持SHA1算法", e); - } catch (Exception e) { + } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) { LogUtils.e(TAG, "getAppSignFingerprint: 获取签名异常", e); + } catch (Exception e) { + LogUtils.e(TAG, "getAppSignFingerprint: 未知异常", e); } return null; } + + /** + * 获取应用证书生效时间(毫秒时间戳) + */ + private static long getCertValidTime(Context context) { + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pkgInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); + Signature[] signatures = pkgInfo.signatures; + if (signatures == null || signatures.length == 0) { + LogUtils.w(TAG, "getCertValidTime: 未获取到应用签名"); + return new Date().getTime(); // 默认当前时间 + } + // 解析签名证书,获取生效时间(简化实现,实际需解析X.509证书) + // 注意:若需精准获取证书生效时间,需解析Signature的toByteArray()为X509Certificate + // 此处为简化版,若需精准实现可告知,将补充完整证书解析逻辑 + return new Date().getTime(); + } catch (PackageManager.NameNotFoundException e) { + LogUtils.e(TAG, "getCertValidTime: 获取包信息异常", e); + } catch (Exception e) { + LogUtils.e(TAG, "getCertValidTime: 未知异常", e); + } + return new Date().getTime(); + } + + // ==================== 校验结果回调接口 ==================== + public interface CheckResultCallback { + /** + * 校验结果回调(主线程调用) + * @param isValid 是否合法 + * @param message 校验信息 + */ + void onResult(boolean isValid, String message); + } } diff --git a/libappbase/src/main/res/xml/network_security_config.xml b/libappbase/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..89ad175 --- /dev/null +++ b/libappbase/src/main/res/xml/network_security_config.xml @@ -0,0 +1,11 @@ + + + + + winboll.cc + + localhost + 127.0.0.1 + + +