Ver Fonte

新增sm2和后端解密

xusl há 2 anos atrás
pai
commit
ebe4f15de8

+ 5 - 0
backend/pom.xml

@@ -187,6 +187,11 @@
             <artifactId>expiringmap</artifactId>
             <version>0.5.8</version>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15to18</artifactId>
+            <version>1.72</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 2 - 4
backend/src/main/java/com/jiayue/ssi/config/WebSecurityConfig.java

@@ -1,9 +1,6 @@
 package com.jiayue.ssi.config;
 
-import com.jiayue.ssi.filter.InterfaceLimitFilter;
-import com.jiayue.ssi.filter.JwtAuthenticationTokenFilter;
-import com.jiayue.ssi.filter.MailCodeFilter;
-import com.jiayue.ssi.filter.VerifyCodeFilter;
+import com.jiayue.ssi.filter.*;
 import com.jiayue.ssi.handler.CustomAuthenticationFailureHandler;
 import com.jiayue.ssi.handler.CustomAuthenticationSuccessHandler;
 import com.jiayue.ssi.handler.EntryPointUnauthorizedHandler;
@@ -59,6 +56,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Override
     protected void configure(HttpSecurity httpSecurity) throws Exception {
+        httpSecurity.addFilterBefore(new VerifySmFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new InterfaceLimitFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
         httpSecurity.addFilterBefore(new MailCodeFilter(), UsernamePasswordAuthenticationFilter.class);

+ 1 - 0
backend/src/main/java/com/jiayue/ssi/constant/CacheConstants.java

@@ -46,4 +46,5 @@ public class CacheConstants
      * 登录账户密码错误次数 redis key
      */
     public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
 }

+ 24 - 0
backend/src/main/java/com/jiayue/ssi/constant/SecretKeyConstants.java

@@ -0,0 +1,24 @@
+package com.jiayue.ssi.constant;/**
+* 密钥类
+*
+* @author xsl
+* @since 2023/02/27
+*/
+public class SecretKeyConstants {
+    /**
+     * 客户端密钥对(公钥)
+     */
+    public static final String CLIENT_PUBLIC_KEY = "042b5cd2658c0b670f9c83aa57ac2df7b3c72ad96b66fc467d716c9be1ba8a107a10857f9b3739d323867a09c27c72a6be024be1cdc3bceb7e1eda16c7f168898d";
+    /**
+     * 客户端密钥对(私钥)
+     */
+    public static final String CLIENT_PRIVATE_KEY = "79507190f53fb88717551c5a784f3f54627cbcb92d35a531ce4630f12fe62a40";
+    /**
+     * 服务端密钥对(公钥)
+     */
+    public static final String SERVER_PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54";
+    /**
+     * 服务端密钥对(私钥)
+     */
+    public static final String SERVER_PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25";
+}

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/controller/UserLoginController.java

@@ -1,5 +1,6 @@
 package com.jiayue.ssi.controller;
 
+import com.jiayue.ssi.annotation.InterfaceLimit;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.service.SysUserService;
@@ -46,6 +47,7 @@ public class UserLoginController {
      * @throws IOException
      */
     @GetMapping("/getVerifyCode")
+    @InterfaceLimit(value = 2,time = 1000)
     public ResponseVO getVerifyCode(HttpServletResponse httpServletResponse) throws IOException {
         // gif类型
         // GifCaptcha captcha = new GifCaptcha(130, 48);
@@ -81,6 +83,7 @@ public class UserLoginController {
         Map<String, String> dataMap = new HashMap<>(16);
         dataMap.put("uuid", uuid);
         dataMap.put("imgBase64", substring);
+        dataMap.put("captchaText", captcha.text());
         return ResponseVO.success(dataMap);
     }
 
@@ -92,6 +95,7 @@ public class UserLoginController {
      * @throws IOException
      */
     @PostMapping("/getMailCode")
+    @InterfaceLimit(time=5000)
     public ResponseVO getMailCode(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
         throws Exception {
         String username = httpServletRequest.getParameter("username");

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/filter/MailCodeFilter.java

@@ -23,7 +23,7 @@ import lombok.RequiredArgsConstructor;
 * @since 2023/02/20
 */
 @RequiredArgsConstructor
-@Order(3)
+@Order(6)
 public class MailCodeFilter extends OncePerRequestFilter {
     private String defaultFilterProcessUrl = "/user/login";
 

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/filter/VerifyCodeFilter.java

@@ -23,7 +23,7 @@ import lombok.RequiredArgsConstructor;
 * @since 2023/02/20
 */
 @RequiredArgsConstructor
-@Order(2)
+@Order(5)
 public class VerifyCodeFilter extends OncePerRequestFilter {
     private String defaultFilterProcessUrl = "/user/login";
 

+ 72 - 0
backend/src/main/java/com/jiayue/ssi/servlet/ParameterRequestWrapper.java

@@ -0,0 +1,72 @@
+package com.jiayue.ssi.servlet;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/**
+ * request参数重新封装
+ *
+ * @author xsl
+ * @since 2023/02/27
+ */
+public class ParameterRequestWrapper extends HttpServletRequestWrapper {
+
+    private Map<String, String[]> params = new HashMap<>();
+
+    public ParameterRequestWrapper(HttpServletRequest request) {
+        super(request);
+        this.params.putAll(request.getParameterMap());
+    }
+
+    public ParameterRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) {
+        this(request);
+        //这里将扩展参数写入参数表
+        addAllParameters(extendParams);
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return new Vector(params.keySet()).elements();
+    }
+
+    @Override
+    public String getParameter(String name) {
+        String[] values = params.get(name);
+        if (values == null || values.length == 0) {
+            return null;
+        }
+        return values[0];
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        String[] values = params.get(name);
+        if (values == null || values.length == 0) {
+            return null;
+        }
+        return values;
+    }
+
+    private void addAllParameters(Map<String, Object> otherParams) {
+        for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
+            addParameter(entry.getKey(), entry.getValue());
+        }
+    }
+
+    private void addParameter(String name, Object value) {
+        if (value != null) {
+            if (value instanceof String[]) {
+                params.put(name, (String[]) value);
+            } else if (value instanceof String) {
+                params.put(name, new String[]{(String) value});
+            } else {
+                params.put(name, new String[]{String.valueOf(value)});
+            }
+        }
+    }
+}

+ 7 - 0
backend/src/main/resources/application.yml

@@ -73,3 +73,10 @@ mybatis:
     type: mysql #数据库类型 目前只支持mysql
 mybatis-plus:
   mapper-locations: classpath:/mapper/*Mapper.xml
+
+# 服务端密钥
+# 客户端公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEz9IxRa8TDtsQABm/zl1VCFGVjyklybVfoVKupC759hevR7R9R8sS4flOFJbk8z++Pp/YSb9aHNDMR+S6SpuAXg==
+# 自己私钥:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgm/zcWRJO89xIxOT1PtowqYZHQFBhik3pccpEY1y1+UmgCgYIKoEcz1UBgi2hRANCAAQdHjJPTUumQK2kkouvR7m9J61iACpPA5c7eLqFB1g85j12X90YAcF6ma30bXXarlULzEPhLTWBbEDIk8wgWy6T
+# 客户端密钥
+# 服务端公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEHR4yT01LpkCtpJKLr0e5vSetYgAqTwOXO3i6hQdYPOY9dl/dGAHBepmt9G112q5VC8xD4S01gWxAyJPMIFsukw==
+# 自己私钥:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgHFgMNlizvBSLrq0DBio/cGEy1WwKj5Y8CmZUnTSdr8OgCgYIKoEcz1UBgi2hRANCAATP0jFFrxMO2xAAGb/OXVUIUZWPKSXJtV+hUq6kLvn2F69HtH1HyxLh+U4UluTzP74+n9hJv1oc0MxH5LpKm4Be

+ 36 - 0
backend/src/test/java/com/jiayue/ssi/service/CreateSm2Key.java

@@ -0,0 +1,36 @@
+package com.jiayue.ssi.service;
+
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.crypto.BCUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.SM2;
+import org.bouncycastle.crypto.engines.SM2Engine;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+
+/**
+* 生成sm2密钥
+*
+* @author xsl
+* @since 2023/02/28
+*/
+public class CreateSm2Key {
+    public static void main(String[] args) {
+        SM2 sm = SmUtil.sm2();
+
+        // sm2的加解密时有两种方式即 C1C2C3、 C1C3C2,
+        sm.setMode(SM2Engine.Mode.C1C3C2);
+
+        // 生成私钥
+        String privateKey = HexUtil.encodeHexStr(sm.getPrivateKey().getEncoded());
+        System.out.println("私钥:"+ privateKey);
+        // 生成公钥
+        String publicKey = HexUtil.encodeHexStr(sm.getPublicKey().getEncoded());
+        System.out.println("公钥:"+ publicKey);
+        // 生成私钥D
+        String privateKeyD = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm.getPrivateKey())); // ((BCECPrivateKey) privateKey).getD().toByteArray();
+        System.out.println("私钥D:"+ privateKeyD);
+        // 生成公钥Q,以q值做为js端的加密公钥
+        String publicKeyQ = HexUtil.encodeHexStr(((BCECPublicKey) sm.getPublicKey()).getQ().getEncoded(false));
+        System.out.println("公钥Q:"+ publicKeyQ);
+    }
+}

+ 29 - 63
backend/src/test/java/com/jiayue/ssi/service/Test.java

@@ -1,72 +1,38 @@
 package com.jiayue.ssi.service;
 
-import com.backcore.constant.WeightValue;
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.BCUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.SM2;
+import cn.hutool.json.JSONUtil;
+import com.jiayue.ssi.constant.SecretKeyConstants;
+import com.jiayue.ssi.util.LocalCache;
+import com.jiayue.ssi.util.SM2CryptUtils;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
+import java.util.Map;
 
 /**
- * TODO
- *
- * @author xsl
- * @version 3.0
- */
+* test
+*
+* @author xsl
+* @since 2023/02/22
+*/
 public class Test {
     public static void main(String[] args) throws Exception{
-//        List<String> joList = new ArrayList<>();
-//        for (int i=0;i<7;i++){
-//            int j = 6-i;
-//            joList.add(i+":"+j);
-//            System.out.println(i+":"+j);
-//        }
-//        System.out.println("===============");
-//        List<String> qjList = new ArrayList<>();
-//        for (int i=0;i<=6;i++){
-//            int j = 6 - i;
-//            for (int k=0;k<=j;k++){
-//                int m = j-k;
-//                qjList.add(i+":"+k+":"+m);
-//                System.out.println(i+":"+k+":"+m);
-//            }
-//        }
-//
-//        List<String> zhList = new ArrayList<>();
-//        for (String joStr:joList){
-//            for (String qjStr:qjList){
-//                zhList.add(joStr+"-"+qjStr);
-//            }
-//        }
-//        System.out.println(zhList.size());
-
-//        SsqData s1 = new SsqData();
-//        s1.setIssue("030045");
-//        SsqData s2 = new SsqData();
-//        s2.setIssue("001730");
-//
-//        List<SsqData> list = new ArrayList();
-//        list.add(s1);
-//        list.add(s2);
-//        List<SsqData> list1 = list.stream().sorted(Comparator.comparing(SsqData::getIssue)).collect(Collectors.toList());
-////        list.stream().sorted(Comparator.comparing(SsqData::getIssue));
-//        for (SsqData ssqData:list1){
-//            System.out.println(ssqData.getIssue());
-//        }
-//        Float.parseFloat()
-        WeightValue totalWeightValue = new WeightValue();
-        totalWeightValue.setR5(10);
-        totalWeightValue.setR1(5);
-
-        Field[] field = totalWeightValue.getClass().getDeclaredFields();
-        // 遍历所有属性
-        for (int j = 0; j < field.length; j++) {
-            // 获取属性的名字
-            String name = field[j].getName();
-            if ("r".equals(name.substring(0,1))){
-                name = name.substring(0, 1).toUpperCase() + name.substring(1);
-                Method m = totalWeightValue.getClass().getMethod("get" + name);
-                System.out.println(name+"=>"+m.invoke(totalWeightValue));
-            }
-        }
-
+        SM2 sm2 = SmUtil.sm2();
+        //这里会自动生成对应的随机秘钥对
+        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
+        //这里默认公钥压缩  公钥的第一个字节用于表示是否压缩 02或者03表示是压缩公钥,04表示未压缩公钥
+//        byte[] publicKey = BCUtil.encodeECPublicKey(sm2.getPublicKey());
+        //这里得到未压缩的公钥
+         byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
+        String priKey = HexUtil.encodeHexStr(privateKey);
+        String pubKey = HexUtil.encodeHexStr(publicKey);
+        System.out.println(priKey);
+        System.out.println(pubKey);
     }
 }

+ 1 - 0
ui/package.json

@@ -28,6 +28,7 @@
     "particles.js": "^2.0.0",
     "path-to-regexp": "2.4.0",
     "qrcodejs2": "0.0.2",
+    "sm-crypto": "^0.3.12",
     "sortablejs": "^1.10.2",
     "vue": "2.6.10",
     "vue-router": "3.0.6",

+ 60 - 12
ui/src/main.js

@@ -55,19 +55,28 @@ VXETable.setup({
 
 Vue.prototype.$axios.interceptors.request.use(
     config => {
-        // do something before request is sent
-        /*    if (store.getters.token) {
-          // let each request carry token
-          // ['X-Token'] is a custom headers key
-          // please modify it according to the actual situation
-          config.headers['Authorization'] = getToken()
-        }*/
-
-        if (getBrowserToken()) { // 判断是否存在token,如果存在的话,则每个http header都加上token
-            config.headers['Authorization'] = getBrowserToken()
-            sessionStorage.setItem('user', getBrowserUser())
+      // get请求映射params参数
+      if (config.method === 'get' && config.params) {
+        let url = config.url + '?' + tansParams(config.params);
+        url = url.slice(0, -1);
+        config.params = {};
+        config.url = url;
+      }
+      if (config.method === 'post' || config.method === 'put') {
+        if (config.url!='/getMailCode'){
+          let encryptParam = doEncrypt(config.data.toString())
+          let result = 'secretData='+encryptParam
+          config.data = result
         }
-        return config
+      }
+      return config
+
+// console.log(config)
+//         if (getBrowserToken()) { // 判断是否存在token,如果存在的话,则每个http header都加上token
+//             config.headers['Authorization'] = getBrowserToken()
+//             sessionStorage.setItem('user', getBrowserUser())
+//         }
+//         return config
     },
     error => {
         // do something with request error
@@ -213,3 +222,42 @@ new Vue({
     store,
     render: h => h(App)
 })
+
+// 加密:
+export function doEncrypt(msgString) {
+  // let msg = msgString;
+  // if (typeof (msgString) !== 'string') {
+  //   msg = JSON.stringify(msgString);
+  // }
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 公钥Q
+  let publicKey2 = '04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54'
+  // 加密结果
+  let encryptData = sm2.doEncrypt(msgString, publicKey2, cipherMode);
+  // 加密后的密文前需要添加04,后端才能正常解密
+  // let encrypt = '04' + encryptData;
+  return encryptData;
+}
+
+// 解密
+export function doDecryptStr(enStr) {
+  let msg = enStr;
+  if (typeof (enStr) !== 'string') {
+    msg = JSON.stringify(enStr);
+  }
+
+  let sm2 = require('sm-crypto').sm2;
+  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
+  let cipherMode = 1
+  // 私钥
+  let privateKey1 = '79507190f53fb88717551c5a784f3f54627cbcb92d35a531ce4630f12fe62a40'
+  // 加密后的密文,需要前去掉04。因为doDecrypt中自行添加了04,后端加密代码也自行添加了04
+  let en = enStr.data.substr(2)
+  // 解密结果
+  let doDecrypt = sm2.doDecrypt(en, privateKey1, cipherMode);
+  // 解密后类型转换
+  let objData = JSON.parse(doDecrypt)
+  return objData;
+}

+ 8 - 5
ui/src/views/login/index.vue

@@ -79,6 +79,8 @@
 
 <script>
 
+import {doEncrypt} from "@/main";
+
 export default {
   name: 'Login',
   data() {
@@ -103,6 +105,7 @@ export default {
       // 文本
       sendBtnText: '点击发送邮箱',
       captchaUrl: '/getVerifyCode',
+      captchaText:'',
       loginRules: {
         /*  username: [{ required: true, trigger: 'blur', validator: validateUsername }],
           password: [{ required: true, trigger: 'blur', validator: validatePassword }]*/
@@ -189,6 +192,7 @@ export default {
       this.$axios.get('/getVerifyCode').then((res) => {
         this.verifyuuid = res.data.uuid
         this.captchaUrl = 'data:image/gif;base64,' + res.data.imgBase64;
+        this.captchaText = res.data.captchaText
       })
     },
     updateCaptcha() {
@@ -200,10 +204,11 @@ export default {
         if (valid) {
           this.loading = true
           let verifycodetemp = this.loginForm.verifyCode
-          let mailboxtemp = this.loginForm.mailbox
-          if (verifycodetemp.length!=4){
+          if (this.captchaText.toLowerCase()!=verifycodetemp.toLowerCase()){
+            this.$message.error('验证码录入错误!')
+            this.updateCaptcha()
+            this.loading = false
             return
-
           }
           const param = new URLSearchParams()
           param.append('username', this.loginForm.username)
@@ -214,7 +219,6 @@ export default {
           this.$axios.post('/user/login', param).then((res) => {
             const {data} = res
             // sessionStorage.setItem('token', data)
-
             document.cookie = "token=" + data;
             document.cookie = "user=".concat(this.loginForm.username)
             sessionStorage.setItem('user', this.loginForm.username)
@@ -229,7 +233,6 @@ export default {
             this.reset()
             this.loading = false
           })
-
         } else {
           console.log('error submit!!')
           return false