/** * Alipay.com Inc. Copyright (c) 2004-2019 All Rights Reserved. */ package com.alipay.easysdk.kernel.util; import com.alipay.easysdk.kernel.AlipayConstants; import java.util.LinkedList; /** * 待验签原文提取器 *

* 注:此处不可使用JSON反序列化工具进行提取,会破坏原有格式,对于签名而言差个空格都会验签不通过 * * @author zhongyu * @version $Id: SignContentExtractor.java, v 0.1 2019年12月19日 9:07 PM zhongyu Exp $ */ public class SignContentExtractor { /** * 左大括号 */ public static final char LEFT_BRACE = '{'; /** * 右大括号 */ public static final char RIGHT_BRACE = '}'; /** * 双引号 */ public static final char DOUBLE_QUOTES = '"'; /** * 获取待验签的原文 * * @param body 网关的整体响应字符串 * @param method 本次调用的OpenAPI接口名称 * @return 待验签的原文 */ public static String getSignSourceData(String body, String method) { // 加签源串起点 String rootNode = method.replace('.', '_') + AlipayConstants.RESPONSE_SUFFIX; String errorRootNode = AlipayConstants.ERROR_RESPONSE; int indexOfRootNode = body.indexOf(rootNode); int indexOfErrorRoot = body.indexOf(errorRootNode); if (indexOfRootNode > 0) { return parseSignSourceData(body, rootNode, indexOfRootNode); } else if (indexOfErrorRoot > 0) { return parseSignSourceData(body, errorRootNode, indexOfErrorRoot); } else { return null; } } private static String parseSignSourceData(String body, String rootNode, int indexOfRootNode) { //第一个字母 + 长度 + 冒号 + 引号 int signDataStartIndex = indexOfRootNode + rootNode.length() + 2; int indexOfSign = body.indexOf("\"" + AlipayConstants.SIGN_FIELD + "\""); if (indexOfSign < 0) { return null; } SignSourceData signSourceData = extractSignContent(body, signDataStartIndex); //如果提取的待验签原始内容后还有rootNode if (body.lastIndexOf(rootNode) > signSourceData.getEndIndex()) { throw new RuntimeException("检测到响应报文中有重复的" + rootNode + ",验签失败。"); } return signSourceData.getSourceData(); } private static SignSourceData extractSignContent(String str, int begin) { if (str == null) { return null; } int beginIndex = extractBeginPosition(str, begin); if (beginIndex >= str.length()) { return null; } int endIndex = extractEndPosition(str, beginIndex); return new SignSourceData(str.substring(beginIndex, endIndex), beginIndex, endIndex); } private static int extractBeginPosition(String responseString, int begin) { int beginPosition = begin; //找到第一个左大括号(对应响应的是JSON对象的情况:普通调用OpenAPI响应明文) //或者双引号(对应响应的是JSON字符串的情况:加密调用OpenAPI响应Base64串),作为待验签内容的起点 while (beginPosition < responseString.length() && responseString.charAt(beginPosition) != LEFT_BRACE && responseString.charAt(beginPosition) != DOUBLE_QUOTES) { ++beginPosition; } return beginPosition; } private static int extractEndPosition(String responseString, int beginPosition) { //提取明文验签内容终点 if (responseString.charAt(beginPosition) == LEFT_BRACE) { return extractJsonObjectEndPosition(responseString, beginPosition); } //提取密文验签内容终点 else { return extractJsonBase64ValueEndPosition(responseString, beginPosition); } } private static int extractJsonBase64ValueEndPosition(String responseString, int beginPosition) { for (int index = beginPosition; index < responseString.length(); ++index) { //找到第2个双引号作为终点,由于中间全部是Base64编码的密文,所以不会有干扰的特殊字符 if (responseString.charAt(index) == DOUBLE_QUOTES && index != beginPosition) { return index + 1; } } //如果没有找到第2个双引号,说明验签内容片段提取失败,直接尝试选取剩余整个响应字符串进行验签 return responseString.length(); } private static int extractJsonObjectEndPosition(String responseString, int beginPosition) { //记录当前尚未发现配对闭合的大括号 LinkedList braces = new LinkedList(); //记录当前字符是否在双引号中 boolean inQuotes = false; //记录当前字符前面连续的转义字符个数 int consecutiveEscapeCount = 0; //从待验签字符的起点开始遍历后续字符串,找出待验签字符串的终止点,终点即是与起点{配对的} for (int index = beginPosition; index < responseString.length(); ++index) { //提取当前字符 char currentChar = responseString.charAt(index); //如果当前字符是"且前面有偶数个转义标记(0也是偶数) if (currentChar == DOUBLE_QUOTES && consecutiveEscapeCount % 2 == 0) { //是否在引号中的状态取反 inQuotes = !inQuotes; } //如果当前字符是{且不在引号中 else if (currentChar == LEFT_BRACE && !inQuotes) { //将该{加入未闭合括号中 braces.push(LEFT_BRACE); } //如果当前字符是}且不在引号中 else if (currentChar == RIGHT_BRACE && !inQuotes) { //弹出一个未闭合括号 braces.pop(); //如果弹出后,未闭合括号为空,说明已经找到终点 if (braces.isEmpty()) { return index + 1; } } //如果当前字符是转义字符 if (currentChar == '\\') { //连续转义字符个数+1 ++consecutiveEscapeCount; } else { //连续转义字符个数置0 consecutiveEscapeCount = 0; } } //如果没有找到配对的闭合括号,说明验签内容片段提取失败,直接尝试选取剩余整个响应字符串进行验签 return responseString.length(); } private static class SignSourceData { /** * 待验签原始内容 */ private final String sourceData; /** * 待验签原始内容在响应字符串中的起始位置 */ private final int beginIndex; /** * 待验签原始内容在响应字符串中的结束位置 */ private final int endIndex; SignSourceData(String sourceData, int beginIndex, int endIndex) { this.sourceData = sourceData; this.beginIndex = beginIndex; this.endIndex = endIndex; } String getSourceData() { return sourceData; } public int getBeginIndex() { return beginIndex; } int getEndIndex() { return endIndex; } } }