diff --git a/pom.xml b/pom.xml
index 166d20fcd..d85aa06be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -237,6 +237,12 @@
spring-boot-starter-webflux
+
+ commons-codec
+ commons-codec
+ 1.12
+
+
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/ShopifyRequestVerifyException.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/ShopifyRequestVerifyException.java
new file mode 100644
index 000000000..c72e75060
--- /dev/null
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/ShopifyRequestVerifyException.java
@@ -0,0 +1,7 @@
+package au.com.royalpay.payment.manage.shopify.auth.domain;
+
+public class ShopifyRequestVerifyException extends RuntimeException{
+ public ShopifyRequestVerifyException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/entity/ShopifyCommonParameter.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/entity/ShopifyCommonParameter.java
new file mode 100644
index 000000000..c5f56f2d0
--- /dev/null
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/entity/ShopifyCommonParameter.java
@@ -0,0 +1,20 @@
+package au.com.royalpay.payment.manage.shopify.auth.domain.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShopifyCommonParameter {
+ private String shop;
+ private String code;
+ private String state;
+ private String hmac;
+ private String host;
+ private String timestamp;
+
+}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyAuthService.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyAuthService.java
index 111839af2..751f33fed 100644
--- a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyAuthService.java
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyAuthService.java
@@ -39,7 +39,7 @@ public class ShopifyAuthService {
private RestTemplate restTemplate;
public ShopifyPermissionURL shopifyPermission(String shopifyStoreHost, String hmac, String timestamp) {
- String redirectUri = PlatformEnvironment.getEnv().concatUrl("/auth.html#/shopify/login");
+ String redirectUri = PlatformEnvironment.getEnv().concatUrl("/auth.html");
String permissionUrl = String.format(PERMISSION_URL, shopifyStoreHost, clientId, scope, redirectUri, String.valueOf(new Date().getTime()).substring(0,10));
return ShopifyPermissionURL.builder().url(permissionUrl).build();
}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidator.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidator.java
new file mode 100644
index 000000000..b3b6ec49f
--- /dev/null
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidator.java
@@ -0,0 +1,30 @@
+package au.com.royalpay.payment.manage.shopify.auth.domain.service;
+
+import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyCommonParameter;
+import au.com.royalpay.payment.manage.shopify.support.HmacVerificationUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ShopifyRequestValidator {
+
+ @Value("${shopify.auth.apiSecretKey}")
+ private String clientSecret;
+
+ public Boolean valid(ShopifyCommonParameter parameter) {
+ StringBuilder message =new StringBuilder();
+ message.append("code=").append(parameter.getCode())
+ .append("&host=").append(parameter.getHost())
+ .append("&shop=").append(parameter.getShop())
+ .append("&state=").append(parameter.getState())
+ .append("×tamp=").append(parameter.getTimestamp());
+ return HmacVerificationUtil.hmacSHA256(message.toString(),clientSecret,parameter.getHmac());
+ }
+
+ public boolean verifyPermission(String shopifyStoreHost, String hmac, String timestamp) {
+ StringBuilder message =new StringBuilder();
+ message.append("shop=").append(shopifyStoreHost)
+ .append("×tamp=").append(timestamp);
+ return HmacVerificationUtil.hmacSHA256(message.toString(),clientSecret,hmac);
+ }
+}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthController.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthController.java
index 3e4183838..8c4a4e117 100644
--- a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthController.java
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/ShopifyAuthController.java
@@ -1,8 +1,12 @@
package au.com.royalpay.payment.manage.shopify.auth.web;
+import au.com.royalpay.payment.manage.shopify.auth.domain.ShopifyRequestVerifyException;
import au.com.royalpay.payment.manage.shopify.auth.domain.application.ShopifyMerchantAuthApplication;
import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyAccessToken;
+import au.com.royalpay.payment.manage.shopify.auth.domain.service.ShopifyRequestValidator;
import au.com.royalpay.payment.manage.shopify.auth.web.command.ShopifyPermissionRequest;
+import au.com.royalpay.payment.manage.shopify.auth.web.command.ShopifyVerifyRequest;
+import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -21,6 +25,23 @@ public class ShopifyAuthController {
@Autowired
private ShopifyMerchantAuthApplication shopifyMerchantAuthApplication;
+ @Autowired
+ private ShopifyRequestValidator shopifyRequestValidator;
+
+ /**
+ * 校验shopify请求
+ *
+ * @param request
+ * @return
+ */
+ @PostMapping("/verify")
+ public JSONObject verifyRequest(@RequestBody @Valid ShopifyVerifyRequest request) {
+ if (!shopifyRequestValidator.valid(request.build())) {
+ throw new ShopifyRequestVerifyException("This request parameters is invalid");
+ }
+ return new JSONObject();
+ }
+
/**
* 获取shopify店铺授权URL
*
@@ -29,6 +50,9 @@ public class ShopifyAuthController {
*/
@PostMapping("/install")
public ShopifyAccessToken shopifyPermission(@RequestBody @Valid ShopifyPermissionRequest request) {
+ if (!shopifyRequestValidator.valid(request.build())) {
+ throw new ShopifyRequestVerifyException("This request parameters is invalid");
+ }
ShopifyAccessToken shopifyAccessToken = shopifyMerchantAuthApplication.install(request);
return shopifyAccessToken;
}
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 11a36c17e..b94a92bd9 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
@@ -1,7 +1,9 @@
package au.com.royalpay.payment.manage.shopify.auth.web;
+import au.com.royalpay.payment.manage.shopify.auth.domain.ShopifyRequestVerifyException;
import au.com.royalpay.payment.manage.shopify.auth.domain.application.ShopifyMerchantAuthApplication;
import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyPermissionURL;
+import au.com.royalpay.payment.manage.shopify.auth.domain.service.ShopifyRequestValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@@ -16,6 +18,9 @@ public class ShopifyAuthTemplateController {
@Autowired
private ShopifyMerchantAuthApplication shopifyMerchantAuthApplication;
+ @Autowired
+ private ShopifyRequestValidator shopifyRequestValidator;
+
/**
* shopify店铺安装入口
*
@@ -25,9 +30,12 @@ public class ShopifyAuthTemplateController {
* @return
*/
@GetMapping("/auth")
- public RedirectView shopifyStoreInstall(@RequestParam("shop") String shopifyStoreHost,
+ public RedirectView shopifyStorePermission(@RequestParam("shop") String shopifyStoreHost,
@RequestParam("hmac") String hmac,
@RequestParam("timestamp") String timestamp) {
+ if (!shopifyRequestValidator.verifyPermission(shopifyStoreHost, hmac, timestamp)) {
+ throw new ShopifyRequestVerifyException("This request parameters is invalid");
+ }
ShopifyPermissionURL shopifyPermissionURL = shopifyMerchantAuthApplication.getShopifyPermissionUrl(shopifyStoreHost, hmac, timestamp);
return new RedirectView(shopifyPermissionURL.getUrl());
}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyPermissionRequest.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyPermissionRequest.java
index fc5cab5f6..d2190a921 100644
--- a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyPermissionRequest.java
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyPermissionRequest.java
@@ -1,5 +1,6 @@
package au.com.royalpay.payment.manage.shopify.auth.web.command;
+import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyCommonParameter;
import au.com.royalpay.payment.manage.shopify.store.web.command.CreateShopifyMerchantCommand;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -7,6 +8,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
@Data
@Builder
@@ -14,9 +16,6 @@ import javax.validation.constraints.NotBlank;
@AllArgsConstructor
public class ShopifyPermissionRequest {
- @NotBlank(message = "Shop can not blank")
- private String shop;
-
@NotBlank(message = "Login Id can not blank")
private String loginId;
@@ -26,12 +25,43 @@ public class ShopifyPermissionRequest {
@NotBlank(message = "Code can not blank")
private String code;
+ @NotBlank(message = "hmac can not blank")
+ private String hmac;
+
+ @NotBlank(message = "host can not blank")
+ private String host;
+
+ @NotBlank(message = "Shop can not blank")
+ @Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.myshopify\\.com",message = "Shop hostname is invalid")
+ private String shop;
+
+ @NotBlank(message = "state can not blank")
+ private String state;
+
+ @NotBlank(message = "timestamp can not blank")
+ private String timestamp;
+
public static ShopifyPermissionRequest instanceOf(CreateShopifyMerchantCommand command) {
return ShopifyPermissionRequest.builder()
.loginId(command.getPaymentAccount().getLoginId())
.password(command.getPaymentAccount().getPassword())
- .shop(command.getShopifyShop())
.code(command.getCode())
+ .hmac(command.getHmac())
+ .host(command.getHost())
+ .shop(command.getShopifyShop())
+ .state(command.getState())
+ .timestamp(command.getTimestamp())
+ .build();
+ }
+
+ public ShopifyCommonParameter build() {
+ return ShopifyCommonParameter.builder()
+ .code(code)
+ .hmac(hmac)
+ .host(host)
+ .shop(shop)
+ .state(state)
+ .timestamp(timestamp)
.build();
}
}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyVerifyRequest.java b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyVerifyRequest.java
new file mode 100644
index 000000000..76a89f34d
--- /dev/null
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/auth/web/command/ShopifyVerifyRequest.java
@@ -0,0 +1,41 @@
+package au.com.royalpay.payment.manage.shopify.auth.web.command;
+
+import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyCommonParameter;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+public class ShopifyVerifyRequest {
+
+ @NotBlank(message = "Code can not blank")
+ private String code;
+
+ @NotBlank(message = "hmac can not blank")
+ private String hmac;
+
+ @NotBlank(message = "host can not blank")
+ private String host;
+
+ @NotBlank(message = "Shop can not blank")
+ @Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.myshopify\\.com", message = "Shop hostname is invalid")
+ private String shop;
+
+ @NotBlank(message = "state can not blank")
+ private String state;
+
+ @NotBlank(message = "timestamp can not blank")
+ private String timestamp;
+
+ public ShopifyCommonParameter build() {
+ return ShopifyCommonParameter.builder()
+ .code(code)
+ .hmac(hmac)
+ .host(host)
+ .shop(shop)
+ .state(state)
+ .timestamp(timestamp)
+ .build();
+ }
+}
diff --git a/src/main/java/au/com/royalpay/payment/manage/shopify/store/web/command/CreateShopifyMerchantCommand.java b/src/main/java/au/com/royalpay/payment/manage/shopify/store/web/command/CreateShopifyMerchantCommand.java
index 35337757c..eb29b9431 100644
--- a/src/main/java/au/com/royalpay/payment/manage/shopify/store/web/command/CreateShopifyMerchantCommand.java
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/store/web/command/CreateShopifyMerchantCommand.java
@@ -4,6 +4,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
@Data
@Accessors(chain = true)
@@ -13,9 +14,22 @@ public class CreateShopifyMerchantCommand {
private PaymentAccountCommand paymentAccount;
+ @NotBlank(message = "Auth code can not blank")
+ private String code;
+
+ @NotBlank(message = "hmac can not blank")
+ private String hmac;
+
+ @NotBlank(message = "host can not blank")
+ private String host;
+
@NotBlank(message = "Shop can not blank")
+ @Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.myshopify\\.com",message = "Shop hostname is invalid")
private String shopifyShop;
- @NotBlank(message = "Auth code can not blank")
- private String code;
+ @NotBlank(message = "state can not blank")
+ private String state;
+
+ @NotBlank(message = "timestamp can not blank")
+ private String timestamp;
}
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
new file mode 100644
index 000000000..5ba85aac0
--- /dev/null
+++ b/src/main/java/au/com/royalpay/payment/manage/shopify/support/HmacVerificationUtil.java
@@ -0,0 +1,58 @@
+package au.com.royalpay.payment.manage.shopify.support;
+
+import org.apache.commons.codec.digest.HmacAlgorithms;
+import org.apache.commons.codec.digest.HmacUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.crypto.RuntimeCryptoException;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
+import java.nio.charset.StandardCharsets;
+import java.security.Security;
+import java.util.Locale;
+
+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");
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ byte[] digest = mac.doFinal(message.getBytes("UTF-8"));
+ String marshal = new HexBinaryAdapter().marshal(digest).toLowerCase(Locale.ROOT);
+ return StringUtils.equals(marshal, hmac);
+ } catch (Exception e) {
+ throw new RuntimeCryptoException("加密异常");
+ }
+ }
+
+ public static boolean hmacSHA256(String input, String key, String hmac) {
+ String encode = encode(input, key, HmacAlgorithms.HMAC_SHA_256);
+ return StringUtils.equals(encode, hmac);
+ }
+
+ 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 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();
+ }
+
+
+}
diff --git a/src/main/ui/static/shopify/auth/shopify.auth.js b/src/main/ui/static/shopify/auth/shopify.auth.js
index d344747c1..a2e317838 100644
--- a/src/main/ui/static/shopify/auth/shopify.auth.js
+++ b/src/main/ui/static/shopify/auth/shopify.auth.js
@@ -35,7 +35,7 @@ define(['angular', 'uiRouter', 'uiBootstrap'], function (angular) {
}).state('shopify.register', {
url: '/register',
templateUrl: '/static/shopify/auth/templates/shopify_register.html',
- params: {'shop': null, 'code': null},
+ params: {'code': null, 'hmac':null, 'host': null,'shop': null, 'state': null,'timestamp':null},
controller: 'ShopifyRegisterController'
});
}]);
@@ -69,14 +69,35 @@ define(['angular', 'uiRouter', 'uiBootstrap'], function (angular) {
module.controller('ShopifyLoginController', ['$scope', '$http', '$state', '$stateParams', '$location', function ($scope, $http, $state, $stateParams, $location) {
var that = $scope;
+
+ var code = getQueryVariable("code")
+ var hmac = getQueryVariable("hmac")
+ var host = getQueryVariable("host")
+ var shop = getQueryVariable("shop")
+ var state = getQueryVariable("state")
+ var timestamp = getQueryVariable("timestamp")
+
that.model = {
- shop: getQueryVariable("shop"),
loginId: '',
password: '',
- code: getQueryVariable("code")
+ code: code,
+ hmac: hmac,
+ host:host,
+ shop: shop,
+ state: state,
+ timestamp: timestamp
}
-
that.loginDisable = false
+
+ that.verifyRequest = function () {
+ $http.post("/shopify/auth/verify", that.model).then(function (res) {
+ }, function (error) {
+ that.resError = error.data.message;
+ that.loginDisable = false
+ })
+ }
+ that.verifyRequest()
+
that.activeShopifyMerchant = function () {
that.loginDisable = true
$http.post("/shopify/auth/install", that.model).then(function (res) {
@@ -89,7 +110,14 @@ define(['angular', 'uiRouter', 'uiBootstrap'], function (angular) {
}
that.registerMerchant = function () {
- $state.go('shopify.register', {shop: getQueryVariable("shop"), code: getQueryVariable("code")});
+ $state.go('shopify.register', {
+ code: code,
+ hmac: hmac,
+ host: host,
+ shop: shop,
+ state: state,
+ timestamp: timestamp
+ });
}
}]);
@@ -241,8 +269,12 @@ define(['angular', 'uiRouter', 'uiBootstrap'], function (angular) {
const param = {
paymentMerchant,
paymentAccount,
+ code: $stateParams.code,
+ hmac: $stateParams.hmac,
+ host: $stateParams.host,
shopifyShop: $stateParams.shop,
- code: $stateParams.code
+ state: $stateParams.state,
+ timestamp: $stateParams.timestamp
}
$http.post('shopify/store/register', param).then(function (resp) {
location.href = resp.data.redirectUrl
diff --git a/src/test/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidatorTest.java b/src/test/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidatorTest.java
new file mode 100644
index 000000000..ceb8c17f0
--- /dev/null
+++ b/src/test/java/au/com/royalpay/payment/manage/shopify/auth/domain/service/ShopifyRequestValidatorTest.java
@@ -0,0 +1,46 @@
+package au.com.royalpay.payment.manage.shopify.auth.domain.service;
+
+import au.com.royalpay.payment.manage.shopify.auth.domain.entity.ShopifyCommonParameter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.regex.Pattern;
+
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@ActiveProfiles({"dev", "alipay", "bestpay", "jd", "wechat", "rpay", "yeepay", "rppaysvc", "common", "alipayplusaps"})
+class ShopifyRequestValidatorTest {
+
+ @Autowired
+ private ShopifyRequestValidator shopifyRequestValidator;
+
+ @Test
+ public void shopifyRequestValidatorTest() {
+ ShopifyCommonParameter parameter = ShopifyCommonParameter.builder()
+ .code("4618ddc9da54cee7be06b35f49c72349")
+ .host("Z2Vlay10ZXN0LXNob3AubXlzaG9waWZ5LmNvbS9hZG1pbg")
+ .shop("geek-test-shop.myshopify.com")
+ .timestamp("1643097047")
+ .state("1643097021")
+ .hmac("e7884f623057afd700b27ba8a5b7529a3f2a2943d2931d73fb82c57f2cf0baaa")
+ .build();
+ Boolean valid = shopifyRequestValidator.valid(parameter);
+ log.warn(String.format("---------------------result: [%s]-------------",valid));
+ }
+
+ @Test
+ public void testShopifyDomain() {
+ String shop = "exampleshop.myshopify.com";
+
+ boolean matches = Pattern.matches("^[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.myshopify\\.com", shop);
+
+ log.warn(String.format("---------------------matches: [%s]-------------",matches));
+ }
+}
\ No newline at end of file