From 1c249b1e3cd2041871df7df974d06ed34608fa17 Mon Sep 17 00:00:00 2001 From: Yixian Date: Thu, 7 Apr 2022 17:17:29 +0800 Subject: [PATCH] shopify hmac optimise --- .../payment/manage/CacheRequestFilter.java | 22 ++++++++ .../payment/manage/WebConfiguration.java | 5 ++ .../web/ShopifyAuthTemplateController.java | 2 +- .../shopify/support/HmacVerificationUtil.java | 50 +++++++++++-------- .../shopify/support/ShopifyHttpUtils.java | 6 +++ .../ShopifyRequestInfoInterceptor.java | 1 + 6 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/main/java/au/com/royalpay/payment/manage/CacheRequestFilter.java diff --git a/src/main/java/au/com/royalpay/payment/manage/CacheRequestFilter.java b/src/main/java/au/com/royalpay/payment/manage/CacheRequestFilter.java new file mode 100644 index 000000000..6d9f72c74 --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/CacheRequestFilter.java @@ -0,0 +1,22 @@ +package au.com.royalpay.payment.manage; + +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class CacheRequestFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + if (request instanceof ContentCachingRequestWrapper) { + filterChain.doFilter(request, response); + } else { + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); + filterChain.doFilter(wrappedRequest, response); + } + } +} diff --git a/src/main/java/au/com/royalpay/payment/manage/WebConfiguration.java b/src/main/java/au/com/royalpay/payment/manage/WebConfiguration.java index c5a511f1e..40594a1c7 100644 --- a/src/main/java/au/com/royalpay/payment/manage/WebConfiguration.java +++ b/src/main/java/au/com/royalpay/payment/manage/WebConfiguration.java @@ -17,6 +17,11 @@ public class WebConfiguration implements WebMvcConfigurer { @Resource private ManagerUserInterceptor managerUserInterceptor; + @Bean + public CacheRequestFilter cacheRequestFilter() { + return new CacheRequestFilter(); + } + @Bean public ShopifyRequestInfoInterceptor shopifyRequestInfoInterceptor() { return new ShopifyRequestInfoInterceptor(); diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthTemplateController.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthTemplateController.java index dcd5c9d70..85dc87aea 100644 --- a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthTemplateController.java +++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthTemplateController.java @@ -40,7 +40,7 @@ public class ShopifyAuthTemplateController { */ @GetMapping("/auth") @ShopifyEndpoint - public String shopifyStorePermission(@RequestParam("shop") String shop, + public String shopifyStorePermission(@RequestParam(value = "shop", required = false) String shop, @RequestParam("hmac") String hmac, HttpServletRequest request) { if (!Pattern.matches("^[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.myshopify\\.com", shop)) { throw new BadRequestException("Parameter shop is invalid."); diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/support/HmacVerificationUtil.java b/src/main/java/au/com/royalpay/payment/manage/shopify/support/HmacVerificationUtil.java index 5c5b159aa..9a27d5464 100644 --- a/src/main/java/au/com/royalpay/payment/manage/shopify/support/HmacVerificationUtil.java +++ b/src/main/java/au/com/royalpay/payment/manage/shopify/support/HmacVerificationUtil.java @@ -1,5 +1,8 @@ package au.com.royalpay.payment.manage.shopify.support; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; import org.apache.commons.lang3.StringUtils; @@ -14,6 +17,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import java.nio.charset.StandardCharsets; import java.security.Security; +import java.util.Arrays; import java.util.Locale; public class HmacVerificationUtil { @@ -25,10 +29,10 @@ public class HmacVerificationUtil { public static boolean checkParameters(String message, String secret, String hmac) { try { Security.addProvider(new BouncyCastleProvider()); - SecretKey secretKey = new SecretKeySpec(secret.getBytes("UTF8"), "HmacSHA256"); + SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); Mac mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey); - byte[] digest = mac.doFinal(message.getBytes("UTF-8")); + byte[] digest = mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); String marshal = new HexBinaryAdapter().marshal(digest).toLowerCase(Locale.ROOT); return StringUtils.equals(marshal, hmac); } catch (Exception e) { @@ -37,28 +41,34 @@ public class HmacVerificationUtil { } public static boolean hmacSHA256(String input, String key, String hmac) { - String encode = encode(input, key, HmacAlgorithms.HMAC_SHA_256); - logger.debug("input={}; key={}; encoded={}; request-hmac: {}", input, key, encode, hmac); - return StringUtils.equals(encode, hmac); + if (isHex(hmac)) { + try { + byte[] requestHmac = Hex.decodeHex(hmac); + byte[] hmacRes = hmac(input, key, HmacAlgorithms.HMAC_SHA_256); + String hmacHex = Hex.encodeHexString(hmacRes); + logger.debug("hex-mode: input={}; key={}; encoded={}; request-hmac: {}", input, key, hmacHex, hmac); + return Arrays.equals(requestHmac, hmacRes); + } catch (DecoderException ignore) { + return false; + } + } else { + //base64 + byte[] hmacRes = hmac(input, key, HmacAlgorithms.HMAC_SHA_256); + String hmacB64 = Base64.encodeBase64String(hmacRes); + logger.debug("b64-mode: input={}; key={}; encoded={}; request-hmac: {}", input, key, hmacB64, hmac); + byte[] requestHmac = Base64.decodeBase64(hmac); + return Arrays.equals(requestHmac, hmacRes); + } } - private static String encode(String input, String key, HmacAlgorithms algorithm) { - Mac mac = HmacUtils.getInitializedMac(algorithm, key.getBytes(StandardCharsets.UTF_8)); - byte[] content = input.getBytes(StandardCharsets.UTF_8); - byte[] signResult = mac.doFinal(content); - return bytesToHex(signResult); + private static boolean isHex(String str) { + return str != null && str.toUpperCase(Locale.ROOT).matches("^[0-9A-F]$"); } - private static String bytesToHex(byte[] hash) { - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); + private static byte[] hmac(String input, String key, HmacAlgorithms algorithm) { + Mac mac = HmacUtils.getInitializedMac(algorithm, key.getBytes(StandardCharsets.UTF_8)); + byte[] content = input.getBytes(StandardCharsets.UTF_8); + return mac.doFinal(content); } diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyHttpUtils.java b/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyHttpUtils.java index 6e82a1643..cd3c64d8f 100644 --- a/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyHttpUtils.java +++ b/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyHttpUtils.java @@ -1,12 +1,18 @@ package au.com.royalpay.payment.manage.shopify.support; +import org.springframework.web.util.ContentCachingRequestWrapper; + import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; public class ShopifyHttpUtils { public static String getRequestBody(HttpServletRequest request) { + if (request instanceof ContentCachingRequestWrapper) { + return new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8); + } BufferedReader br = null; StringBuilder sb = new StringBuilder(""); try { diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyRequestInfoInterceptor.java b/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyRequestInfoInterceptor.java index 268b7619a..0d4c8270b 100644 --- a/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyRequestInfoInterceptor.java +++ b/src/main/java/au/com/royalpay/payment/manage/shopify/support/ShopifyRequestInfoInterceptor.java @@ -31,6 +31,7 @@ public class ShopifyRequestInfoInterceptor extends HandlerInterceptorAdapter { if (HttpMethod.POST.matches(request.getMethod())) { if (AnnotatedElementUtils.isAnnotated(method, ShopifyEndpoint.class)) { + String requestBody = ShopifyHttpUtils.getRequestBody(request); JSONObject body = JSONObject.parseObject(requestBody); String shop = body.getString("shop_domain");