如何用Retrofit 2发送JsonObject?遇无响应及加解密异常问题
咱们一步步来拆解你遇到的两个问题,帮你搞定这个接口调用的麻烦:
问题1:后端收到NameValuePairs而非预期JSON
你用org.json.JSONObject作为Retrofit的@Body参数时,后端收到的是表单格式的NameValuePairs,这是因为Retrofit默认会把org.json.JSONObject当成表单数据处理,而不是JSON格式的请求体。
解决方案
要让Retrofit正确发送JSON请求,得做两个调整:
- 确保你的Retrofit实例配置了Gson转换器(这是处理JSON请求/响应的核心):
val retrofit = Retrofit.Builder() .baseUrl("你的基础URL") .addConverterFactory(GsonConverterFactory.create()) .client(你的OkHttpClient实例) .build()
- 改用Gson的
JsonObject或者类型安全的数据类作为请求体参数:- 用Gson JsonObject的方式:
@POST("getRequest") Call<GenericWrapper> generic(@Body com.google.gson.JsonObject jsonObject);- 更推荐的类型安全方式(创建请求数据类):
然后修改ApiService:data class InitSessionRequest( @SerializedName("dt") val dt: String )@POST("getRequest") Call<GenericWrapper> generic(@Body InitSessionRequest request);
这样调整后,Retrofit就会把请求体序列化为标准JSON格式,后端就能正确接收了。
问题2:改用Gson JsonObject后出现BadPaddingException
这个异常几乎都是加密解密流程不匹配导致的,咱们逐一排查可能的原因:
排查点1:AES加密/解密的配置完全一致吗?
必须确保客户端和后端的AES模式、填充方式、密钥长度完全相同。比如客户端加密用的是AES/CBC/PKCS5Padding,后端解密也得用一模一样的配置,不能一端用PKCS7Padding另一端用NoPadding(Java里PKCS5和PKCS7其实是兼容的,但最好统一)。
检查你的AesUtil实现,比如加密方法应该是这样的:
public String encrypt(byte[] data) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8")); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); byte[] encryptedBytes = cipher.doFinal(data); // 加密后一定要转成Base64字符串,不能直接用new String(encryptedBytes)(会乱码丢数据) return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP); }
解密方法也要对应:
public byte[] decrypt(byte[] encryptedData) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8")); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); return cipher.doFinal(encryptedData); }
排查点2:加密解密的步骤顺序是否完全对称?
你的客户端加密流程是:原始JSON → 字符串 → UTF-8字节 → Base64编码(NO_WRAP) → AES加密 → Base64编码(得到dt字段)
那后端的解密流程必须是:dt字段 → Base64解码 → AES解密 → Base64解码 → UTF-8字符串 → 解析JSON
任何一步顺序错了都会导致解密失败,比如后端如果跳过了Base64解码直接解密,就会抛出BadPaddingException。
排查点3:密钥和IV的一致性
检查你加密用的aesKeyIv.getKey()、aesKeyIv.getInitVector(),和解密时用的apiSingleton.getIv()、apiSingleton.getKey()是否完全一致:
- 有没有在传输过程中被修改?比如后端返回的IV是Base64编码的,你是不是直接当成UTF-8字符串用了?需要先Base64解码再转字节数组。
- 转字节数组时的编码是不是统一用UTF-8?不能一端用UTF-8另一端用默认编码。
排查点4:Base64编码/解码的参数一致
客户端加密前用的是Base64.NO_WRAP,解密后端返回的dt时也是用Base64.NO_WRAP吗?要确保两端的Base64编码规则完全一致(比如是否换行、是否加填充符)。
总结
先搞定请求格式的问题(配置Gson转换器+用Gson JsonObject/数据类),再逐一排查加密解密的配置、步骤、密钥IV的一致性,就能解决这两个问题啦。
内容的提问来源于stack exchange,提问作者user6683139

