From b5eceda5a2deaf23516b619fb93693e51d4bfbc2 Mon Sep 17 00:00:00 2001 From: qiaowei Date: Tue, 6 Aug 2019 09:57:29 +0800 Subject: [PATCH] =?utf8?q?=E9=97=A8=E7=A6=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/com/supwisdom/oauth/oauth.kt | 2 +- .../kotlin/com/supwisdom/oauth/security.kt | 3 +- oauth/src/main/resources/templates/index.html | 15 + oauth/src/main/resources/templates/login.html | 10 +- .../dlpay/api/bean/DoorQRCodeParam.java | 23 ++ .../dlpay/api/bean/DoorQrcodeResponse.java | 17 ++ .../dlpay/paysdk/proxy/ConsumePropxy.java | 2 + .../paysdktest/CitizenCardPayProxyTest.java | 11 + .../supwisdom/dlpay/framework/util/MD5.java | 20 +- .../dlpay/framework/util/RandomUtils.java | 11 +- .../com/supwisdom/dlpay/util/AesUtil.java | 162 +++++++++++ .../supwisdom/dlpay/util/QrCodeTotpUtil.java | 59 ++++ .../supwisdom/dlpay/util/QrcodeRawData.java | 22 ++ .../supwisdom/dlpay/util/RSAKeysGenerate.java | 179 ++++++++++++ .../java/com/supwisdom/dlpay/util/TOTP.java | 264 ++++++++++++++++++ .../com/supwisdom/dlpay/PayApiApplication.kt | 2 +- .../api/controller/consume_api_controller.kt | 29 ++ .../api/service/impl/qrcode_srvice_impl.kt | 155 ++++++++++ .../dlpay/api/service/qrcode_service.kt | 9 + .../com/supwisdom/dlpay/mobile/MobileApi.kt | 28 +- .../dlpay/mobile/domain/TBMobileUser.kt | 6 + .../dlpay/mobile/service/MobileApiService.kt | 2 + .../service/impl/MobileApiServiceImpl.kt | 4 + .../kotlin/com/supwisdom/dlpay/security.kt | 103 +++++-- 24 files changed, 1102 insertions(+), 36 deletions(-) create mode 100644 oauth/src/main/resources/templates/index.html create mode 100644 payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java create mode 100644 payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java create mode 100644 payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java create mode 100644 payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java create mode 100644 payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java create mode 100644 payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java create mode 100644 payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java create mode 100644 payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt create mode 100644 payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt index 8fb70be3..79acca55 100644 --- a/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt +++ b/oauth/src/main/kotlin/com/supwisdom/oauth/oauth.kt @@ -62,7 +62,7 @@ class WebMainController { @GetMapping("/login") fun loginView() = "login" - @GetMapping("/index") + @GetMapping(value = ["/", "/index"]) fun indexView() = "index" } diff --git a/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt index 0fdf8845..191178bf 100644 --- a/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt +++ b/oauth/src/main/kotlin/com/supwisdom/oauth/security.kt @@ -46,6 +46,7 @@ class AuthLoginSuccessHandler : SimpleUrlAuthenticationSuccessHandler() { } user.lastlogin = DateUtil.getNow() oAuthUserService.saveUser(user) + defaultTargetUrl = "/index" super.onAuthenticationSuccess(request, response, authentication) } else { throw UserLoginFailException("登录错误") @@ -131,7 +132,7 @@ class WebSecurityConfig { // 设置 Web MVC 应用权限 http.authorizeRequests() .antMatchers("/login", "/login/form", "/userinfor").permitAll() - .antMatchers("/static/**").permitAll() + .antMatchers("/css/**").permitAll() .antMatchers("/code/image").permitAll() .antMatchers("/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() diff --git a/oauth/src/main/resources/templates/index.html b/oauth/src/main/resources/templates/index.html new file mode 100644 index 00000000..6efd74e6 --- /dev/null +++ b/oauth/src/main/resources/templates/index.html @@ -0,0 +1,15 @@ + + + + + 用户中心 + + + + + + + +用户中心 + + \ No newline at end of file diff --git a/oauth/src/main/resources/templates/login.html b/oauth/src/main/resources/templates/login.html index f3ef3d6e..c6660ad8 100644 --- a/oauth/src/main/resources/templates/login.html +++ b/oauth/src/main/resources/templates/login.html @@ -6,8 +6,8 @@ - - + +

统一身份认证
-
+
- +
@@ -37,7 +37,7 @@
- + \ No newline at end of file diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java new file mode 100644 index 00000000..e047a7a4 --- /dev/null +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQRCodeParam.java @@ -0,0 +1,23 @@ +package com.supwisdom.dlpay.api.bean; + + +import com.supwisdom.dlpay.api.APIRequestParam; +import com.supwisdom.dlpay.api.annotation.Sign; +import com.supwisdom.dlpay.api.exception.RequestParamCheckException; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; + +@Getter +@Setter +public class DoorQRCodeParam extends APIRequestParam { + @Sign + @NotNull(message = "二维码不能为空") + private String qrcode; + + @Override + public boolean checkParam() throws RequestParamCheckException { + return true; + } +} diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java new file mode 100644 index 00000000..dd77797b --- /dev/null +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/api/bean/DoorQrcodeResponse.java @@ -0,0 +1,17 @@ +package com.supwisdom.dlpay.api.bean; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DoorQrcodeResponse extends ApiResponse { + private String userid; + private String citycardno; + private String bankcardno; + private String status; +} diff --git a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java index b5e1f3be..62584f7c 100644 --- a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java +++ b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java @@ -22,4 +22,6 @@ public interface ConsumePropxy { @PostMapping("/thirdpay/finish") ThirdPayResponse thirdpayFinish(@RequestBody ThirdPayfinishParam param); + @PostMapping("/qrcodequery") + DoorQrcodeResponse qrcodequery(@RequestBody DoorQRCodeParam param); } diff --git a/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java b/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java index 36705fe9..8229dcfe 100644 --- a/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java +++ b/payapi-sdk/src/test/java/com/supwisdom/dlpay/paysdktest/CitizenCardPayProxyTest.java @@ -173,6 +173,17 @@ public class CitizenCardPayProxyTest { assertThat("qrcodeConfirm:" + response.getRetmsg() + response.getException()+",query="+response.isRequireQuery(), response.getRetcode(), equalTo(0)); } + @Test + public void qrcodeQuery(){ + ApiLoginHelper helper = new ApiLoginHelper(apiLoginProxy); + helper.login(appid, secret); + DoorQRCodeParam param = new DoorQRCodeParam(); + param.setQrcode("6M4I1KGUO_UOVJVYCW52FQ"); + + DoorQrcodeResponse response = consumePropxy.qrcodequery(param); + assertThat("qrcode query " + response.getRetmsg() + response.getException(), + response.getRetcode(), equalTo(0)); + } @Test public void thirdpay(){ diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java index 91abab4f..bcf44451 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/MD5.java @@ -1,5 +1,7 @@ package com.supwisdom.dlpay.framework.util; +import org.apache.commons.codec.binary.Base64; + import java.security.MessageDigest; public class MD5 { @@ -83,5 +85,21 @@ public class MD5 { } return null; } - + /** 对字符串进行MD5加密 */ + public static String encodeByMD5ToURLSafeBase64(String originString) { + if (originString != null) { + try { + // 创建具有指定算法名称的信息摘要 + MessageDigest md = MessageDigest.getInstance("MD5"); + // 使用指定的字节数组对摘要进行最后更新,然后完成摘要计算 + byte[] results = md.digest(originString.getBytes("utf-8")); + // 将得到的字节数组变成字符串返回 + String resultString = Base64.encodeBase64URLSafeString(results); + return resultString.toUpperCase(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + return null; + } } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java index 0afac381..74a05346 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/RandomUtils.java @@ -64,8 +64,15 @@ public class RandomUtils { String result = s + "_"+savesecret; return result; } - - + + public static String getSecureRandomHex() { + SecureRandom a = new SecureRandom(); + byte[] phonesecret = new byte[10]; + a.nextBytes(phonesecret); + + String savesecret = Hex.encodeHexString(phonesecret); + return savesecret; + } /** * * @param length diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java new file mode 100644 index 00000000..ddd8f91c --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/AesUtil.java @@ -0,0 +1,162 @@ +package com.supwisdom.dlpay.util; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang.StringUtils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class AesUtil { + + public static String encrypt(String rawdata, String secret) { + Cipher cipher; + try { + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8")); + String hexdata = Base64.encodeBase64String(encdata); + + return hexdata; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidKeyException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + + } + public static String encryptCFB(String rawdata, String secret ,String ivstr,String KEY_ALG_MODE) { + Cipher cipher; + try { + //byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF }; + byte[] iv = Hex.decodeHex(ivstr.toCharArray()); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher = Cipher.getInstance(KEY_ALG_MODE); + SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES"); + cipher.init(Cipher.ENCRYPT_MODE, key,ivSpec); + byte[] encdata = cipher.doFinal(rawdata.getBytes("UTF-8")); + String hexdata = Base64.encodeBase64String(encdata); + + return hexdata; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidKeyException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + } catch (DecoderException e) { + e.printStackTrace(); + } + return null; + + } + + public static String decrypt(String encdata, String secret){ + Cipher cipher; + try { + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decordedValue = Base64.decodeBase64(encdata); + byte[] decdata = cipher.doFinal(decordedValue); + String rawdata = new String(decdata); + + return rawdata; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidKeyException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + public static String decryptCFB(String encdata, String secret,String ivstr,String KEY_ALG_MODE){ + Cipher cipher; + try { +// byte[] iv = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF }; + byte[] iv = Hex.decodeHex(ivstr.toCharArray()); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher = Cipher.getInstance(KEY_ALG_MODE); + SecretKeySpec key = new SecretKeySpec(Base64.decodeBase64(secret), "AES"); + cipher.init(Cipher.DECRYPT_MODE, key,ivSpec); + byte[] decordedValue = Base64.decodeBase64(encdata); + byte[] decdata = cipher.doFinal(decordedValue); + String rawdata = new String(decdata); + + return rawdata; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidKeyException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + } catch (DecoderException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java new file mode 100644 index 00000000..fa51bb31 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/QrCodeTotpUtil.java @@ -0,0 +1,59 @@ +package com.supwisdom.dlpay.util; + +public class QrCodeTotpUtil { + public static String generateTOTP(String seed){ + long X = 30; + long T0 = 0; + String steps = "0"; + long time = System.currentTimeMillis() / 1000; + long T = (time - T0) / X; + steps = Long.toHexString(T).toUpperCase(); + while (steps.length() < 16) { + steps = "0" + steps; + } + String totp = TOTP.generateTOTP(seed, steps, "8", "HmacSHA256"); + return totp; + } + + public static String generateTOTP(String seed,String returnDigits){ + long X = 30; + long T0 = 0; + String steps = "0"; + long time = System.currentTimeMillis() / 1000; + long T = (time - T0) / X; + steps = Long.toHexString(T).toUpperCase(); + while (steps.length() < 16) { + steps = "0" + steps; + } + String totp = TOTP.generateTOTP(seed, steps, returnDigits, "HmacSHA256"); + return totp; + } + + public static boolean verifyCode(String totp, String secret, int offset) { + + String second = "30"; + long T0 = 0; + String[] keys = new String[offset * 2 + 1]; + long time = System.currentTimeMillis() / 1000; + for (int i = 0; i < keys.length; i++) { + String steps = "0"; + try { + long T = (time - T0) / Long.parseLong(second); + steps = Long.toHexString(T + (i - offset)).toUpperCase(); + while (steps.length() < 16) { + steps = "0" + steps; + } + String key = TOTP.generateTOTP(secret, steps, "8", "HmacSHA256"); + keys[i] = key; + } catch (final Exception e) { + System.out.println("Error : " + e); + } + } + for (String key : keys) { + if (key.equals(totp)) { + return true; + } + } + return false; + } +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java b/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java new file mode 100644 index 00000000..44ac2884 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/QrcodeRawData.java @@ -0,0 +1,22 @@ +package com.supwisdom.dlpay.util; + +public class QrcodeRawData { + private String totp; + private String userid; + + public String getTotp() { + return totp; + } + + public void setTotp(String totp) { + this.totp = totp; + } + + public String getUserid() { + return userid; + } + + public void setUserid(String userid) { + this.userid = userid; + } +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java b/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java new file mode 100644 index 00000000..4af21485 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/RSAKeysGenerate.java @@ -0,0 +1,179 @@ +package com.supwisdom.dlpay.util; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +public class RSAKeysGenerate { + + public static final String KEY_ALGORITHM = "RSA"; + private static final String PUBLIC_KEY = "RSAPublicKey"; + private static final String PRIVATE_KEY = "RSAPrivateKey"; + private static final String KEY_ALG_MODE = "RSA/ECB/PKCS1Padding"; + /** 密钥大小 */ + private static final int KEY_SIZE = 1024; + /** *//** + * RSA最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = 117; + + /** *//** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = 128; + /** 默认的安全服务提供者 */ + private static final Provider DEFAULT_PROVIDER = new BouncyCastleProvider(); + public static void main(String[] args) { + Map keyMap; + try { + keyMap = initKey(); + String publicKey = getPublicKey(keyMap); + System.out.println(publicKey); + String privateKey = getPrivateKey(keyMap); + System.out.println(privateKey); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String getPublicKey(Map keyMap) throws Exception { + Key key = (Key) keyMap.get(PUBLIC_KEY); + byte[] publicKey = key.getEncoded(); + return encryptBASE64(key.getEncoded()); + } + + public static String getPrivateKey(Map keyMap) throws Exception { + Key key = (Key) keyMap.get(PRIVATE_KEY); + byte[] privateKey = key.getEncoded(); + return encryptBASE64(key.getEncoded()); + } + + + public static String encryptBASE64(byte[] key) throws Exception { + return Base64.encodeBase64String(key); + } + + public static Map initKey() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPairGen.initialize(1024); + KeyPair keyPair = keyPairGen.generateKeyPair(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + Map keyMap = new HashMap(2); + keyMap.put(PUBLIC_KEY, publicKey); + keyMap.put(PRIVATE_KEY, privateKey); + return keyMap; + } + + public static PrivateKey getPrivateKey(final String key) throws Exception { + byte[] keyBytes = null; + keyBytes = com.sun.jersey.core.util.Base64.decode(key); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + public static PublicKey getPublicKey(final String key) throws Exception { + byte[] keyBytes; + keyBytes = com.sun.jersey.core.util.Base64.decode(key); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + return publicKey; + } + + public static byte[] decrypt(PrivateKey privateKey, byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(KEY_ALG_MODE, DEFAULT_PROVIDER); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + + } + public static String decryptbyte(PrivateKey privateKey, String encrypttext) { + if (privateKey == null || StringUtils.isBlank(encrypttext)) { + return null; + } + try { + byte[] en_data = org.apache.commons.codec.binary.Base64.decodeBase64(encrypttext); + + + byte[] data = decrypt(privateKey, en_data); + return new String(data); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } + /** + * 使用指定的公钥加密数据。 + * + * @param publicKey 给定的公钥。 + * @param data 要加密的数据。 + * @return 加密后的数据。 + */ + public static byte[] encrypt(PublicKey publicKey, byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(KEY_ALG_MODE, DEFAULT_PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + } + public static byte[] encryptbyte(PublicKey publicKey, String plaintext) { + if (publicKey == null || plaintext == null) { + return null; + } + byte[] data = plaintext.getBytes(); + try { + byte[] en_data = encrypt(publicKey, data); + return en_data; + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java b/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java new file mode 100644 index 00000000..bcab4d33 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/TOTP.java @@ -0,0 +1,264 @@ +package com.supwisdom.dlpay.util; + +/** + Copyright (c) 2011 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in Section + 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + */ + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.lang.reflect.UndeclaredThrowableException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; + +/** + * This is an example implementation of the OATH TOTP algorithm. Visit + * www.openauthentication.org for more information. + * + * @author Johan Rydell, PortWise, Inc. + */ +public class TOTP { + private static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + private TOTP() { + } + + /** + * This method uses the JCE to provide the crypto algorithm. HMAC computes a + * Hashed Message Authentication Code with the crypto hash algorithm as a + * parameter. + * + * @param crypto + * : the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512) + * @param keyBytes + * : the bytes to use for the HMAC key + * @param text + * : the message or text to be authenticated + */ + private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) { + try { + Mac hmac; + hmac = Mac.getInstance(crypto); + + SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW"); + hmac.init(macKey); + + return hmac.doFinal(text); + } catch (GeneralSecurityException gse) { + throw new UndeclaredThrowableException(gse); + } + } + + /** + * This method converts a HEX string to Byte[] + * + * @param hex + * : the HEX string + * + * @return: a byte array + */ + private static byte[] hexStr2Bytes(String hex) { + // Adding one byte to get the right conversion + // Values starting with "0" can be converted + byte[] bArray = new BigInteger("10" + hex, 16).toByteArray(); + + // Copy all the REAL bytes, not the "first" + byte[] ret = new byte[bArray.length - 1]; + + for (int i = 0; i < ret.length; i++) + ret[i] = bArray[i + 1]; + + return ret; + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key + * : the shared secret, HEX encoded + * @param time + * : a value that reflects a time + * @param returnDigits + * : number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + public static String generateTOTP(String key, String time, + String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA1"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key + * : the shared secret, HEX encoded + * @param time + * : a value that reflects a time + * @param returnDigits + * : number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + public static String generateTOTP256(String key, String time, + String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA256"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key + * : the shared secret, HEX encoded + * @param time + * : a value that reflects a time + * @param returnDigits + * : number of digits to return + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + public static String generateTOTP512(String key, String time, + String returnDigits) { + return generateTOTP(key, time, returnDigits, "HmacSHA512"); + } + + /** + * This method generates a TOTP value for the given set of parameters. + * + * @param key + * : the shared secret, HEX encoded + * @param time + * : a value that reflects a time + * @param returnDigits + * : number of digits to return + * @param crypto + * : the crypto function to use + * + * @return: a numeric String in base 10 that includes + * {@link truncationDigits} digits + */ + public static String generateTOTP(String key, String time, + String returnDigits, String crypto) { + int codeDigits = Integer.decode(returnDigits).intValue(); + String result = null; + + // Using the counter + // First 8 bytes are for the movingFactor + // Compliant with base RFC 4226 (HOTP) + while (time.length() < 16) + time = "0" + time; + + // Get the HEX in a Byte[] + byte[] msg = hexStr2Bytes(time); + byte[] k = hexStr2Bytes(key); + byte[] hash = hmac_sha(crypto, k, msg); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + + int binary = ((hash[offset] & 0x7f) << 24) + | ((hash[offset + 1] & 0xff) << 16) + | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); + + int otp = binary % DIGITS_POWER[codeDigits]; + + result = Integer.toString(otp); + + while (result.length() < codeDigits) { + result = "0" + result; + } + + return result; + } + + public static void main(String[] args) { + long X = 30; + long T0 = 0; + String steps = "0"; + long time = System.currentTimeMillis() / 1000; + System.out.println(time); + long T = (time - T0) / X; + steps = Long.toHexString(T).toUpperCase(); + while (steps.length() < 16) { + steps = "0" + steps; + } + String seed = "9e8bb3d2df73741c041aef39e37ee015fc98233720a1350fcd67d5c3027896ac"; + String totp = TOTP.generateTOTP(seed, steps, "8", "HmacSHA256"); + System.out.println(totp); + + /* + + + SecureRandom a = new SecureRandom(); + byte[] b = a.generateSeed(8); + // Seed for HMAC-SHA1 - 20 bytes + String seed = "3132333435363738393031323334353637383930"; + + // Seed for HMAC-SHA256 - 32 bytes + //String seed32 = "3132333435363738393031323334353637383930313233343536373839303132"; + String seed32 = "e5b72370f61687c8075b8383266f4fbcbb3b55da178eed76329666600c134093"; + + // Seed for HMAC-SHA512 - 64 bytes + String seed64 = "3132333435363738393031323334353637383930" + + "3132333435363738393031323334353637383930" + + "3132333435363738393031323334353637383930" + "31323334"; + long T0 = 0; + long X = 30; + long[] testTime = { 59L, 1111111109L, 1111111111L, 1234567890L, + 2000000000L, 20000000000L }; + + String steps = "0"; + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + + try { + System.out.println("+---------------+-----------------------+" + + "------------------+--------+--------+"); + System.out.println("| Time(sec) | Time (UTC format) " + + "| Value of T(Hex) | TOTP | Mode |"); + System.out.println("+---------------+-----------------------+" + + "------------------+--------+--------+"); + + for (int i = 0; i < testTime.length; i++) { + long T = (testTime[i] - T0) / X; + steps = Long.toHexString(T).toUpperCase(); + + while (steps.length() < 16) + steps = "0" + steps; + + String fmtTime = String.format("%1$-11s", testTime[i]); + String utcTime = df.format(new Date(testTime[i] * 1000)); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + + steps + " |"); + System.out.println(generateTOTP(seed, steps, "8", "HmacSHA1") + + "| SHA1 |"); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + + steps + " |"); + System.out.println(generateTOTP(seed32, steps, "8", + "HmacSHA256") + "| SHA256 |"); + System.out.print("| " + fmtTime + " | " + utcTime + " | " + + steps + " |"); + System.out.println(generateTOTP(seed64, steps, "8", + "HmacSHA512") + "| SHA512 |"); + + System.out.println("+---------------+-----------------------+" + + "------------------+--------+--------+"); + } + } catch (final Exception e) { + System.out.println("Error : " + e); + } + + */ + } +} \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt index 8b7b7967..d993aea6 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt @@ -88,8 +88,8 @@ class JwtAuthFilterRegistration { fun registerMobileFilter(): FilterRegistrationBean { val registrationBean = FilterRegistrationBean() registrationBean.filter = mobileSecurityFilter - registrationBean.order = 2 registrationBean.addUrlPatterns("/mobile/*") + registrationBean.order = 2 return registrationBean } } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt index 7c356837..cfdd7ec3 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt @@ -18,6 +18,7 @@ import com.supwisdom.dlpay.framework.util.* import org.apache.commons.lang3.StringUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext +import org.springframework.data.redis.core.RedisTemplate import org.springframework.http.ResponseEntity import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.* @@ -50,6 +51,12 @@ class ConsumeAPIController { @Autowired private lateinit var agentServiceProxy: AgentServiceProxy + @Autowired + lateinit var redisTemplate: RedisTemplate + + @Autowired + private lateinit var qrCodeService: QRCodeService + /** * ============================================================================ * 消费流水结果查询统一接口 @@ -754,4 +761,26 @@ class ConsumeAPIController { } } } + + /** + * ============================================================================ + * 门禁码查询接口 + * ============================================================================ + * */ + @PostMapping("/qrcodequery") + fun qrcodeQuery(@RequestBody param: DoorQRCodeParam): ResponseEntity { + var token = redisTemplate.opsForValue().get(param.qrcode) + if(token.isNullOrEmpty()){ + return ResponseEntity.ok(ResponseBodyBuilder.create() + .fail(1, "二维码不存在或已失效")) + } + val ret = qrCodeService.decodeCode(token) + return if(ret.retcode==0){ + ResponseEntity.ok(ResponseBodyBuilder.create() + .success(ret, "成功")) + }else{ + ResponseEntity.ok(ResponseBodyBuilder.create() + .fail(ret.retcode, ret.retmsg)) + } + } } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt new file mode 100644 index 00000000..95a1de0e --- /dev/null +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt @@ -0,0 +1,155 @@ +package com.supwisdom.dlpay.api.service.impl + +import com.google.gson.Gson +import com.supwisdom.dlpay.api.bean.ApiResponse +import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse +import com.supwisdom.dlpay.api.service.QRCodeService +import com.supwisdom.dlpay.api.service.UserService +import com.supwisdom.dlpay.framework.service.SystemUtilService +import com.supwisdom.dlpay.framework.util.MD5 +import com.supwisdom.dlpay.framework.util.TradeDict +import com.supwisdom.dlpay.mobile.service.MobileApiService +import com.supwisdom.dlpay.util.AesUtil +import com.supwisdom.dlpay.util.QrCodeTotpUtil +import com.supwisdom.dlpay.util.QrcodeRawData +import com.supwisdom.dlpay.util.RSAKeysGenerate +import mu.KotlinLogging +import org.apache.commons.lang.math.NumberUtils +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.stereotype.Service +import java.time.Duration + +@Service +class QRCodeServiceImpl:QRCodeService{ + @Autowired + private lateinit var systemUtilService: SystemUtilService + @Autowired + private lateinit var mobileApiService: MobileApiService + @Autowired + lateinit var userService: UserService + @Autowired + lateinit var redisTemplate: RedisTemplate + + val logger = KotlinLogging.logger { } + + override fun encodeCode(uid:String): ApiResponse { + var resp = ApiResponse() + val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey") + val iv = systemUtilService.getBusinessValue("aes.cfb.iv") + if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "秘钥未配置" + return resp + } + val muser = mobileApiService.findUserById(uid) + if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){ + resp.retcode = 1 + resp.retmsg = "用户不存在或状态异常" + return resp + } + if(muser.userid.isNullOrEmpty()){ + resp.retcode = 1 + resp.retmsg = "用户未绑定身份" + return resp + } + val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey) + val rowdata = QrcodeRawData() + rowdata.userid = muser.userid + rowdata.setTotp(totp) + val orgData = Gson().toJson(rowdata) + val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic) + val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData)) + val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding") + + val key = MD5.encodeByMD5ToURLSafeBase64(qrcode) + redisTemplate.opsForValue().set(key,qrcode, Duration.ofMinutes(3)) + resp.retcode = 0 + resp.retmsg = key + return resp + } + + override fun decodeCode(qrcode :String): DoorQrcodeResponse { + + //解密 + var resp = DoorQrcodeResponse() + try { + val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey") + val iv = systemUtilService.getBusinessValue("aes.cfb.iv") + val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset") + var offset = 20 + if (NumberUtils.isDigits(totpoffset)) { + offset = Integer.valueOf(totpoffset).toInt() + } + if (rootkey.isNullOrEmpty()||iv.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "秘钥未配置" + return resp + } + + println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]") + val encdataBack = AesUtil.decryptCFB(qrcode, rootkey, iv, "AES/CFB/NoPadding") + if (encdataBack == null) { + resp.retcode = 1 + resp.retmsg = "二维码解析失败" + return resp + } + val uid = encdataBack.substring(0, encdataBack.indexOf(":")) + val muser = mobileApiService.findUserById(uid) + if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){ + resp.retcode = 1 + resp.retmsg = "用户不存在或状态异常" + return resp + } + val seed32 = muser.secertkey + val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1) + + val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate) + val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata) + logger.info("rawdata_back=$rawdataBack") + + val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java) + logger.info("userid=" + rawData.userid) + if (null == rawData || rawData.userid.isNullOrEmpty()) { + resp.retcode = 1 + resp.retmsg = "用户不存在" + return resp + } + val person = userService.findOnePersonByUserid(rawData.userid) + + /*if(TradeDict.STATUS_NORMAL!=person.status){ + resp.retcode = 1 + resp.retmsg = "用户状态异常" + return resp + }*/ + val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset) + return if (verifyBarcode) { + resp.retcode = 0 + resp.retmsg = "OK" + resp.userid = person.userid + resp.status = person.status + val bankcard = mobileApiService.findCardByUserid(person.userid) + if(bankcard!=null){ + resp.bankcardno = bankcard.cardno + } + val citycard = mobileApiService.findCityCardByUserid(person.userid) + if(citycard!=null){ + resp.citycardno = citycard.cardno + } + resp + } else { + resp.retcode = 1 + resp.retmsg = "二维码校验失败" + resp + } + } catch (e: Exception) { + e.printStackTrace() + resp.retcode = 1 + resp.retmsg = "二维码识别异常" + return resp + } + + } + +} + diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt new file mode 100644 index 00000000..029f6a8e --- /dev/null +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/qrcode_service.kt @@ -0,0 +1,9 @@ +package com.supwisdom.dlpay.api.service + +import com.supwisdom.dlpay.api.bean.ApiResponse +import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse + +interface QRCodeService { + fun encodeCode(uid: String): ApiResponse + fun decodeCode(qrcode: String): DoorQrcodeResponse +} \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt index 3e75d864..b929beae 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt @@ -1,9 +1,9 @@ package com.supwisdom.dlpay.mobile -import com.mascloud.sdkclient.Client import com.supwisdom.dlpay.agent.citizencard.YnrccUtil import com.supwisdom.dlpay.agent.service.CitizencardPayService import com.supwisdom.dlpay.api.bean.JsonResult +import com.supwisdom.dlpay.api.service.QRCodeService import com.supwisdom.dlpay.api.service.UserService import com.supwisdom.dlpay.api.util.MobileNumberCheck import com.supwisdom.dlpay.framework.core.JwtConfig @@ -17,9 +17,9 @@ import com.supwisdom.dlpay.mobile.domain.TBMobileUser import com.supwisdom.dlpay.mobile.service.MobileApiService import com.supwisdom.dlpay.system.service.DictionaryProxy import com.supwisdom.dlpay.util.ConstantUtil +import com.supwisdom.dlpay.util.RSAKeysGenerate import org.apache.commons.lang.StringUtils import org.jose4j.jwt.ReservedClaimNames -import org.jose4j.lang.JoseException import org.springframework.beans.factory.annotation.Autowired import org.springframework.data.redis.core.RedisTemplate import org.springframework.http.HttpStatus @@ -95,11 +95,12 @@ class ApiInit { val temp = redisTemplate.opsForValue().get(phone) if (temp.isNullOrEmpty()) { val code = RandomUtils.randomNumber(6) + System.out.println(code) + redisTemplate.opsForValue().set(phone, code, Duration.ofMinutes(5)) val rs = mobileApiService.sendSms(phone, code) if ("0" != rs.retcode) { return JsonResult.error(rs.retmsg) } - redisTemplate.opsForValue().set(phone, code, Duration.ofMinutes(5)) } return JsonResult.ok("验证码已发送") } @@ -196,6 +197,12 @@ class ApiInit { } user.lastlogin = DateUtil.getNow() user.jti = jwt.jti + val keyMap = RSAKeysGenerate.initKey() + val publicKey = RSAKeysGenerate.getPublicKey(keyMap) + val privateKey = RSAKeysGenerate.getPrivateKey(keyMap) + user.rsaprivate = privateKey + user.rsapublic = publicKey + user.secertkey = RandomUtils.getSecureRandomHex() mobileApiService.saveUser(user) redisTemplate.delete(user.uid) var payseted = false @@ -234,11 +241,12 @@ class ApiV1 { lateinit var dictionaryProxy: DictionaryProxy @Autowired lateinit var citizencardPayService: CitizencardPayService - @Autowired lateinit var apiJwtRepository: ApiJwtRepository @Autowired lateinit var jwtConfig: JwtConfig + @Autowired + lateinit var qrcodeService:QRCodeService @RequestMapping("/idtypes") fun idtypes(): JsonResult { @@ -693,11 +701,13 @@ class ApiV1 { @RequestMapping("/qrcode") fun qrcode(): JsonResult { val p = SecurityContextHolder.getContext().authentication - mobileApiService.findUserById(p.name) + val user = mobileApiService.findUserById(p.name) ?: return JsonResult.error("用户不存在,请注册") - //TODO qrcode - return JsonResult.ok("ok").put("qrcode", p.name)!! + val resp = qrcodeService.encodeCode(user.uid) + return if(resp.retcode==0){ + JsonResult.ok("ok").put("qrcode", resp.retmsg)!! + }else{ + JsonResult.error(resp.retmsg) + } } - - } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt index 68b91554..276d8d00 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/domain/TBMobileUser.kt @@ -160,6 +160,12 @@ class TBMobileUser : UserDetails { * */ @Column(name = "ulogo", length = 100) var ulogo: String? = null + @Column(name = "rsaprivate", length = 1000) + var rsaprivate: String? = null + @Column(name = "rsapublic", length = 1000) + var rsapublic: String? = null + @Column(name = "secertkey", length = 64) + var secertkey: String? = null fun checkLoginpwdtime():Int{ diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt index 5e1ad771..0311352f 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt @@ -18,6 +18,8 @@ interface MobileApiService { fun findCardByUserid(userid :String) :TCard? + fun findCityCardByUserid(userid :String) :TCard? + fun saveCard(card:TCard):TCard fun sendSms(phone:String,code:String):BaseResp diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt index 87ef47b5..cc1bc470 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt @@ -60,6 +60,10 @@ class MobileApiServiceImpl : MobileApiService { return cardDao.findCardByUseridAndCardtype(userid,ConstantUtil.CARDTYPE_BANKCARD) } + override fun findCityCardByUserid(userid: String): TCard? { + return cardDao.findCardByUseridAndCardtype(userid,ConstantUtil.CARDTYPE_CITIZENCARD) + } + override fun saveCard(card: TCard): TCard { return cardDao.save(card) } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt index 5dc7de14..154dee4d 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/security.kt @@ -41,6 +41,7 @@ import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource import org.springframework.web.filter.OncePerRequestFilter +import org.springframework.web.servlet.config.annotation.CorsRegistry import java.security.SecureRandom import java.util.* import javax.servlet.FilterChain @@ -131,15 +132,84 @@ class ApiJwtAuthenticationFilter : OncePerRequestFilter() { } @Component -class MobileSecurityFilter : HttpFilter() { - override fun doFilter(req: HttpServletRequest, res: HttpServletResponse, chain: FilterChain) { -// val url = req.requestURI -// if (url.contains("/userinfor")) { -// SecurityContextHolder.getContext().authentication = RedisTokenStore(redisConnectionFactory).readAuthentication(jwt) -// chain.doFilter(req, res) -// return -// } - chain.doFilter(req, res) +class MobileSecurityFilter : OncePerRequestFilter() { + @Autowired + lateinit var jwtConfig: JwtConfig + + @Autowired + lateinit var apiJwtRepository: ApiJwtRepository + + private var jwtUtil: JwtTokenUtil? = null + + private fun getUtil(): JwtTokenUtil { + if (jwtUtil == null) { + jwtUtil = JwtTokenUtil((jwtConfig)) + } + return jwtUtil as JwtTokenUtil + } + + override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { + + request.getHeader(jwtConfig.header)?.let { authHeader -> + try { + val jwt = if (authHeader.startsWith(jwtConfig.tokenHeader)) { + authHeader.substring(jwtConfig.tokenHeader.length) + } else { + throw JoseException("JWT Header error") + } + val claims = getUtil().verifyToken(jwt) + apiJwtRepository.findById(claims[ReservedClaimNames.JWT_ID].toString()).let { + if (!it.isPresent) { + throw JoseException("JWT has not been register") + } + // token 已被设为黑名单 + if (it.get().status != TradeDict.JWT_STATUS_NORMAL) { + throw JoseException("JWT status error : ${it.get().status}") + } + } + if (jwtConfig.multiTenant) { + val tenantId = request.getHeader(Constants.HEADER_TETANTID) + if (tenantId == null) { + response.status = HttpStatus.UNAUTHORIZED.value() + return + } + if (claims[Constants.JWT_CLAIM_TENANTID] != tenantId) { + response.status = HttpStatus.UNAUTHORIZED.value() + return + } + TenantContext.setTenantSchema(tenantId) + } + val auth = UsernamePasswordAuthenticationToken(claims[Constants.JWT_CLAIM_UID], null, + (claims[Constants.JWT_CLAIM_AUTHORITIES] as ArrayList<*>) + .map { SimpleGrantedAuthority(it as String) }) + SecurityContextHolder.getContext().authentication = auth + } catch (e: InvalidJwtException) { + SecurityContextHolder.clearContext() + if (e.hasExpired()) { + // jwt 过期后返回 401 + apiJwtRepository.deleteById(e.jwtContext.jwtClaims.jwtId) + } + response.status = HttpStatus.UNAUTHORIZED.value() + return + } catch (e: JoseException) { + SecurityContextHolder.clearContext() + // jwt 失效后返回 401 + response.status = HttpStatus.UNAUTHORIZED.value() + response.contentType = "application/json;charset=UTF-8" + return + } catch (e: Exception) { + SecurityContextHolder.clearContext() + // jwt 失效后返回 401 + response.status = HttpStatus.UNAUTHORIZED.value() + response.contentType = "application/json;charset=UTF-8" + return + } + } + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", "*"); + response.setHeader("Access-Control-Allow-Credentials", "true") + filterChain.doFilter(request, response) } } @@ -151,12 +221,15 @@ class WebSecurityConfig { @Configuration @Order(1) class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() { - + @Autowired + lateinit var apiJwtAuthenticationFilter: ApiJwtAuthenticationFilter override fun configure(http: HttpSecurity) { // 设置 API 访问权限管理 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .antMatcher("/api/**") + .addFilterAfter(apiJwtAuthenticationFilter, + UsernamePasswordAuthenticationFilter::class.java) .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .antMatchers("/api/notify/**").permitAll() @@ -188,9 +261,9 @@ class WebSecurityConfig { @Autowired lateinit var userDetailsService: MobileUserService - @Autowired - lateinit var apiFilter: ApiJwtAuthenticationFilter + lateinit var mobileSecurityFilter: MobileSecurityFilter + override fun configure(auth: AuthenticationManagerBuilder) { auth.authenticationProvider(userProvider()) @@ -220,7 +293,7 @@ class WebSecurityConfig { .cors() .and() .antMatcher("/mobileapi/**") - .addFilterAfter(apiFilter, + .addFilterAfter(mobileSecurityFilter, UsernamePasswordAuthenticationFilter::class.java) .authorizeRequests().antMatchers("/mobileapi/i/**", "/mobileapi/login") .permitAll().anyRequest().authenticated() @@ -230,8 +303,6 @@ class WebSecurityConfig { .failureHandler(failureHandler) .successHandler(successHandler) .and().csrf().disable() - .sessionManagement().maximumSessions(1) - .expiredUrl("/mobileapi/sessionexpired") } @Bean @@ -242,7 +313,7 @@ class WebSecurityConfig { configuration.allowedMethods = listOf("GET", "POST") configuration.allowedHeaders = listOf("*") val source = UrlBasedCorsConfigurationSource() - source.registerCorsConfiguration("/mobileapi/**", configuration); + source.registerCorsConfiguration("/mobileapi/**", configuration) return source } } -- 2.17.1