首页 > 编程语言 >微信小程序【同城配送】及【加密请求】

微信小程序【同城配送】及【加密请求】

时间:2023-11-07 18:45:35浏览次数:42  
标签:JsonObject 配送 String url 微信 ctx new 加密 local

在小程序后台配置API安全时注意保存密钥,要不然还得重新弄。

  1. 封装属性配置类,在加解密的时候会用到
  2. 封装加解密方法
  3. 使用okhttp封装post加密请求,并将信息解密
  4. 调用post方法将必要信息加密后发送给微信并得到相应,对其解密
  5. 对信息进行业务处理

封装属性配置类

创建一个config.properties,并将其添加到.gitignore中,避免泄露重要信息。

@Configuration
@PropertySource("classpath:config.properties") //读取配置文件
@ConfigurationProperties(prefix="api")
public class ApiSecurityConfig {
    static String sym_key;
    static String sym_sn;
    static String asym_sn;
    static String appid;
    // getters and setters
}

加密

public class AES_Enc {
    private static JsonObject getReqForSign(JsonObject ctx, JsonObject req) {
        Gson gson = new Gson();
        // 开发者本地信息
        String local_appid = ctx.get("local_appid").getAsString();
        String url_path = ctx.get("url_path").getAsString();
        String local_sym_sn = ctx.get("local_sym_sn").getAsString();
        String local_sym_key = ctx.get("local_sym_key").getAsString();
        //加密签名使用的统一时间戳
        long localTs = System.currentTimeMillis() / 1000;
        String nonce = generateNonce();

        req.addProperty("_n", nonce);
        req.addProperty("_appid", local_appid);
        req.addProperty("_timestamp", localTs);
        String plaintext = gson.toJson(req);

        String aad = url_path + "|" + local_appid + "|" + localTs + "|" + local_sym_sn;
        byte[] realKey = Base64.getDecoder().decode(local_sym_key);
        byte[] realIv = generateRandomBytes(12);
        byte[] realAad = aad.getBytes(StandardCharsets.UTF_8);
        byte[] realPlaintext = plaintext.getBytes(StandardCharsets.UTF_8);

        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(realKey, "AES");
            GCMParameterSpec parameterSpec = new GCMParameterSpec(128, realIv);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, parameterSpec);
            cipher.updateAAD(realAad);

            byte[] ciphertext = cipher.doFinal(realPlaintext);
            byte[] encryptedData = Arrays.copyOfRange(ciphertext, 0, ciphertext.length - 16);
            byte[] authTag = Arrays.copyOfRange(ciphertext, ciphertext.length - 16, ciphertext.length);

            String iv = base64Encode(realIv);
            String data = base64Encode(encryptedData);
            String authtag = base64Encode(authTag);

            JsonObject reqData = new JsonObject();
            reqData.addProperty("iv", iv);
            reqData.addProperty("data", data);
            reqData.addProperty("authtag", authtag);

            JsonObject reqforsign = new JsonObject();
            reqforsign.addProperty("req_ts", localTs);
            reqforsign.addProperty("req_data", reqData.toString());

            return reqforsign;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private static String generateNonce() {
        byte[] nonce = generateRandomBytes(16);
        return base64Encode(nonce).replace("=", "");
    }

    private static byte[] generateRandomBytes(int length) {
        byte[] bytes = new byte[length];
        new SecureRandom().nextBytes(bytes);
        return bytes;
    }

    private static String base64Encode(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }

    private static JsonObject getCtx(String url) {
        JsonObject ctx = new JsonObject();
        // 仅做演示,敏感信息请勿硬编码
        ctx.addProperty("local_sym_key", ApiSecurityConfig.getSym_key());
        ctx.addProperty("local_sym_sn", ApiSecurityConfig.getSym_sn());
        ctx.addProperty("local_appid", ApiSecurityConfig.getAppid());
        ctx.addProperty("url_path", url);

        return ctx;
    }

    private static JsonObject getRawReq(Object hashMap) {
        Gson gson = new Gson();
        String json = gson.toJson(hashMap);
        JsonParser parser = new JsonParser();
        return parser.parse(json).getAsJsonObject();
    }

    public static JsonObject getData(Object param, String url) {
        JsonObject req = getRawReq(param);
        JsonObject ctx = getCtx(url);
        return getReqForSign(ctx, req);
    }


}

解密

public class AES_Dec {
    public static HashMap<String, Object> getRealResp(JsonObject ctx, JsonObject resp) {
        byte[] decryptedBytes = null;
        // 开发者本地信息
        String local_appid = ctx.get("local_appid").getAsString();
        String url_path = ctx.get("url_path").getAsString();
        String local_sym_sn = ctx.get("local_sym_sn").getAsString();
        String local_sym_key = ctx.get("local_sym_key").getAsString();
        // API响应数据,解密只需要响应头时间戳与响应数据
        long respTs = resp.get("resp_ts").getAsLong();
        String respData = resp.get("resp_data").getAsString();

        JsonParser parser = new JsonParser();
        JsonElement resp_data = parser.parse(respData);
        String iv = resp_data.getAsJsonObject().get("iv").getAsString();
        String data = resp_data.getAsJsonObject().get("data").getAsString();
        String authtag = resp_data.getAsJsonObject().get("authtag").getAsString();
        // 构建AAD
        String aad = url_path + "|" + local_appid + "|" + respTs + "|" + local_sym_sn;
        // 拼接cipher和authtag
        byte[] dataBytes = Base64.getDecoder().decode(data);
        byte[] authtagBytes = Base64.getDecoder().decode(authtag);
        byte[] new_dataBytes = new byte[dataBytes.length + authtagBytes.length];
        System.arraycopy(dataBytes, 0, new_dataBytes, 0, dataBytes.length);
        System.arraycopy(authtagBytes, 0, new_dataBytes, dataBytes.length, authtagBytes.length);
        byte[] aadBytes = aad.getBytes(StandardCharsets.UTF_8);
        byte[] ivBytes = Base64.getDecoder().decode(iv);
        HashMap<String, Object> realResp = null;
        try {
            byte[] keyBytes = Base64.getDecoder().decode(local_sym_key);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
            cipher.updateAAD(aadBytes);
            try {
                decryptedBytes = cipher.doFinal(new_dataBytes);
            } catch (Exception e) {
                System.out.println("auth tag验证失败");
                return null;
            }

            // 解密结果
            String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8);
            JsonElement element = parser.parse(decryptedData);
            Gson gson = new Gson();
            realResp = gson.fromJson(element, HashMap.class);
            long localTs = System.currentTimeMillis() / 1000;
            // 安全检查,根据业务实际需求判断
            if (element.getAsJsonObject().get("_appid").getAsString() == local_appid // appid不匹配
                    || element.getAsJsonObject().get("_timestamp").getAsLong() != respTs // timestamp与Wechatmp-TimeStamp不匹配
                    || localTs - element.getAsJsonObject().get("_timestamp").getAsLong() > 300 // 响应数据的时候与当前时间超过5分钟
            ) {
                System.out.println("安全字段校验失败");
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return realResp;

    }

    private static JsonObject getCtx(String url) {
        JsonObject ctx = new JsonObject();
        ctx.addProperty("local_sym_key", ApiSecurityConfig.getSym_key());
        ctx.addProperty("local_sym_sn", ApiSecurityConfig.getSym_sn());
        ctx.addProperty("local_appid", ApiSecurityConfig.getAppid());
        ctx.addProperty("url_path", url);
        return ctx;
    }

    private static JsonObject getResp(String respData, Long resp_ts) {
        JsonObject resp = new JsonObject();
        // String respData = "{"iv":"xx","data":"yyy","authtag":"zzz"}";
        resp.addProperty("resp_ts", resp_ts);
        resp.addProperty("resp_data", respData);

        return resp;
    }

    public static HashMap<String, Object> getRealRespResult(String data, Long ts, String url) {
        JsonObject resp = getResp(data, ts);
        JsonObject ctx = getCtx(url);
        return getRealResp(ctx, resp);
    }
}

封装请求

这里使用了OkHttpClient

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>

文档所有接口请求方式均为HTTPS-POST

public class RequestUtil {
    private static final OkHttpClient client = new OkHttpClient();
    private static final Logger log = LoggerFactory.getLogger(RequestUtil.class);

    public static String mapToQueryString(Map<String, String> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }

    public static HashMap<String, Object> doSecurityPost(String url, Object params, HashMap<String, Object> headers)  {
        try (Response response = post(url, params, headers)) {

            ResponseBody body = response.body();
            String respSign = response.header("Wechatmp-Signature");
            String respAppId = response.header("Wechatmp-Appid");
            String respTs = response.header("Wechatmp-TimeStamp");
            String respSerial = response.header("Wechatmp-Serial");
            if (body == null || respTs == null) {
                throw HttpException.badRequest("微信加密请求失败");
            }

            String responseBodyString = null;
            try {
                responseBodyString = body.string();
            } catch (IOException e) {
                System.out.println(e.getMessage());
                log.error("获得response body string失败,原因:{}", e.getMessage());
                throw new RuntimeException(e);
            }
            // 解密数据  {errcode=934016.0, errmsg=Order not exist rid: 65192df5-77a7d1b3-117973ad, _n=270eaa5fe4b9e68213ecbd37f417e10e, _appid=wx1e933945b62aebf8, _timestamp=1.696148981E9}
            return AES_Dec.getRealRespResult(responseBodyString, Long.decode(respTs), url.split("\\?")[0]); 
        }
    }

    public static Response post(String url, Object params, HashMap<String, Object> headers)  {
        RequestBody requestBody;
        String json = "";
        if (params instanceof String) {
            json = params.toString();
        } else if (params != null) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                json = objectMapper.writeValueAsString(params);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        requestBody = RequestBody.create(ByteString.encodeUtf8(json), MediaType.parse("application/json; charset=utf-8"));

        Request.Builder requestBuilder = new Request.Builder().url(url)
                .post(requestBody);
        if (headers != null) {
            for (Map.Entry<String, Object> entry : headers.entrySet()) {
                requestBuilder.addHeader(entry.getKey(), entry.getValue().toString());
            }
        }

        Request request = requestBuilder.build();
        try {
            return client.newCall(request).execute();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

调用加密方法请求配送价格

这里以preaddorder为例。

// 不同的接口参数不同,ExpressInfo为查询运费价格所需的参数封装类
public HashMap<String, Object> getExpressFeeResponse(ExpressInfo expressInfo, String path) {
    if (Objects.nonNull(expressInfo) && expressInfo.isValid()) {
        String url = getConcatUrl(path);
        JsonObject data = AES_Enc.getData(expressInfo, path);
        String reqData = data.get("req_data").getAsString();
        return RequestUtil.doSecurityPost(url, reqData, getWeChatHeader(data, path));
    }
    throw HttpException.badRequest("获取配送信息参数不正确");
}

public HashMap<String, Object> getWeChatHeader(JsonObject data, String url) {
    HashMap<String, Object> headers = new HashMap<>();
    headers.put("Wechatmp-Appid", ApiSecurityConfig.getAppid());
    String signature = RSA_Sign.getSign(data, url);
    headers.put("Wechatmp-Signature", signature);
    long localTs = data.get("req_ts").getAsLong();
    headers.put("Wechatmp-TimeStamp", Long.toString(localTs));
    return headers;
}

getExpressFeeResponse(expressInfo, ExpressInterface.CALCULATE_PRICE.getPath());

public enum ExpressInterface {
    CALCULATE_PRICE("preaddorder"),
    CREATE_ORDER("addorder"),
    CANCEL_ORDER("cancelorder"),
    QUERY_ORDER("queryorder");

    private final String path;

    ExpressInterface(String path) {
        this.path = path;
    }

    public String getPath() {
        String BASE_URL = "https://api.weixin.qq.com/cgi-bin/express/intracity/";
        return BASE_URL + path;
    }
}

到这里应该就没啥问题了。
这是官方的文档,可以进行参考。
同城配送
服务端api签名指南

标签:JsonObject,配送,String,url,微信,ctx,new,加密,local
From: https://www.cnblogs.com/shames/p/17815015.html

相关文章

  • 微信怎么批量保存大量照片
    8-2本文要解决的问题是自动或者快速地保存微信收到的图片的事情,如果你的工作中有一个事情是需要每天或者经常保存大量的从微信收到的图片或者视频的,也许本文适合你,本文介绍的方法,可以自动保存各个群或者人发来的图片和视频。 首先要准备一些工具:1,一台你日常用来工作的电脑2,你的工......
  • 第4章 高级加密标准
    4.1高级加密标准的起源严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的分组和密钥长度:AES的分组长度固定为128比特,密钥长度则可以是128比特、192比特或256比特;而Rijndael使用的密钥和分组长度可以是32比特的整数倍,以......
  • 微信支付:wxpay.unifiedOrder(data)返回appid 与 openId 不配
    原因:小程序和APP、公众号等支付方式夸端口调用支付,后台配置多个appId时A程序中的openid在B程序中支付。即使用A程序的openid和B程序的appIdy去调用wxpay.unifiedOrder(data)把请求统一支付的参数输出:得到当前的appid,微信返回后看到另一个Appid,如果两个一致,则不会出现不匹配问题......
  • 企业微信开启接收消息+验证URL有效性
    企业微信开启接收消息+验证URL有效性......
  • 对称加密与非对称加密有什么区别?RSA和AES算法有什么区别?
    对称加密与非对称加密有什么区别?对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是如何安全将密钥传输给另一方。常见的对称加密算法有:DES、AES等。非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私......
  • inno setup 加密算法
    innosetup5.5可以对数据进行加密,密码使用SHA1算法相关函数可以搜索字符串"PasswordCheckHash"Hash计算过程CODE:00484C4453pushebxCODE:00484C4581C450FEFFFFaddesp,0FFFFFE50h......
  • Apifox 自动登录 + 请求加密[自用]
    varjsrsasign=require("jsrsasign");varcryptoJs=require("crypto-js");varaccessToken=pm.environment.get("ACCESS_TOKEN");varaccessTokenExpires=pm.environment.get("ACCESS_TOKEN_EXPIRES");letrequest_enc......
  • 如何快速导出、备份微信通讯录好友联系人微信号
    6-9如果因工作需要,你有多个微信,并且你的业务开发的客户都在这些微信里,将会面临一个问题,那就是备份问题,因为通过微信做业务,如果遇到微信不小心被封号,或者离职的交接等情况,客户联系方式的损失是影响比较大的。所以一定要及时对微信的通讯录备份下来,如果你的习惯比较好,会将客户的手机......
  • uniapp+微信小程序 激励广告
    防忘首先在onready里面准备好代码 包括广告准备就绪 准备出错,广告关闭data(){return{showAd:false,canShowAd:false,rewardedVideoAd:null,}},onReady()......
  • 电脑端如何打开两个微信?
    作为一个程序员还问这个问题?不觉得有点丢人么?哈哈,其实我刚开始也不会,这不弄好了之后,就赶紧来做个功课;只需三步,轻松解决:第一步,右击微信-属性,复制起始位置的文件路径 第二步:桌面新建一个文本文件,记事本打开,复制下面的代码并粘贴,记得修改文件路径(代码中红色的部分),保存。1......