如何校验RESTful接口仅被Android应用调用而非CURL、Postman等工具
技术实现方案
1. 核心请求签名校验机制
- 客户端预埋专属密钥:给每个合法Android版本分配独有的AppSecret,打包时编译进APK的so库中,不要硬编码在Java/Kotlin层避免反编译直接获取
- 签名规则统一:客户端每次发起请求时,将
请求路径+请求参数+当前时间戳+设备唯一标识+AppSecret拼接后做SHA256加密生成签名字段,和时间戳、设备标识一起放在自定义请求头里传输 - 服务端校验逻辑:Quarkus端编写全局拦截器,收到请求后首先判断请求携带的时间戳和服务器时间差是否超过5分钟(防止重放攻击),再用完全相同的规则生成签名和请求头携带的签名比对,不一致直接返回403
2. Android应用身份合法性校验
- 包名+签名证书指纹校验:客户端请求时上报APK的包名和签名证书的SHA256指纹,服务端提前存储官方应用的包名和指纹白名单,比对不通过直接拦截
注意:指纹获取逻辑要放在native层实现,避免被篡改后上报假的指纹信息
3. 运行环境安全校验
- 客户端在发起请求前先做运行环境检测:包括是否是模拟器、是否被Xposed/Frida等Hook工具注入、是否被二次打包,检测到风险环境直接拒绝发起请求
- 可叠加自定义UA校验,设置专属的UA规则,不符合规则的请求直接拦截,该规则为基础防护,不可单独作为校验依据
4. Quarkus服务端落地实现
可以通过JAX-RS全局请求拦截器实现所有校验逻辑,示例代码如下:
@Provider @PreMatching public class OfficialAppRequestFilter implements ContainerRequestFilter { // 提前配置好的官方App白名单、AppSecret等信息,可放到Quarkus配置文件中 private static final Set<String> VALID_PACKAGE_NAMES = Set.of("com.你的官方应用包名"); private static final Set<String> VALID_CERT_FINGERPRINTS = Set.of("你的官方签名证书SHA256指纹"); private static final String APP_SECRET = "预埋到客户端so库的专属密钥"; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // 1. 提取自定义请求头参数 String timestamp = requestContext.getHeaderString("X-App-Timestamp"); String signature = requestContext.getHeaderString("X-App-Signature"); String packageName = requestContext.getHeaderString("X-App-Package"); String certFingerprint = requestContext.getHeaderString("X-App-Cert-Fingerprint"); String deviceId = requestContext.getHeaderString("X-App-Device-Id"); // 基础参数非空校验 if (Stream.of(timestamp, signature, packageName, certFingerprint, deviceId).anyMatch(Objects::isNull)) { requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); return; } // 2. 时间差校验,防止重放攻击 long reqTime = Long.parseLong(timestamp); if (Math.abs(System.currentTimeMillis() - reqTime) > 5 * 60 * 1000) { requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); return; } // 3. 包名和证书指纹校验 if (!VALID_PACKAGE_NAMES.contains(packageName) || !VALID_CERT_FINGERPRINTS.contains(certFingerprint)) { requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); return; } // 4. 签名校验(请求体需要提前做缓存处理,避免流被消费后后续接口无法读取) String rawSignContent = requestContext.getUriInfo().getPath() + 缓存的请求体字符串 + timestamp + deviceId + APP_SECRET; String serverSign = DigestUtils.sha256Hex(rawSignContent); if (!serverSign.equals(signature)) { requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); return; } } }
5. 补充防护建议
- 所有接口必须走HTTPS传输,防止中间人抓包篡改请求头和参数
- 密钥和签名规则定期迭代更新,App端通过热更或者版本强制升级适配新规则
- 增加异常请求频率限制,短时间内多次校验失败的IP直接封禁一段时间,防止暴力破解
内容的提问来源于stack exchange,提问作者Prakash Dutta
相关产品推荐
相关产品推荐

