add shopify request verify

master
ycfxx 3 years ago
parent 4bcdf212c5
commit 296fafef74

@ -237,6 +237,12 @@
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
</dependencies>
<build>
<plugins>

@ -0,0 +1,7 @@
package au.com.royalpay.payment.manage.shopify.auth.domain;
public class ShopifyRequestVerifyException extends RuntimeException{
public ShopifyRequestVerifyException(String message) {
super(message);
}
}

@ -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;
}

@ -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();
}

@ -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("&timestamp=").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("&timestamp=").append(timestamp);
return HmacVerificationUtil.hmacSHA256(message.toString(),clientSecret,hmac);
}
}

@ -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();
}
/**
* shopifyURL
*
@ -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;
}

@ -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());
}

@ -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();
}
}

@ -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();
}
}

@ -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;
}

@ -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();
}
}

@ -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

@ -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));
}
}
Loading…
Cancel
Save