Vue + Springboot uses the national secret algorithm SM2 for data encryption and transmission at the front and back ends, and provides a complete interactive solution

When deploying the external network of the project, data encryption transmission is often required, especially for the projects of enterprises and institutions. In addition, for security or red header files, the national secret algorithm is often required. Because interaction is involved, SM2 asymmetric encryption is used.

Back end (Springboot)

(1) Main dependencies required (if there are other deficiencies, Baidu can do it by itself):

        <!-- hutool Tool class -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.10</version>
        </dependency>

        <!-- Third party encryption function dependency -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.68</version>
        </dependency>

(2) Creation of SM2 function method

Bloggers here make the relevant encryption and decryption functions into functional interfaces at the back end, so the code of the service implementation class is directly displayed here, and the unified return parameters inside can be changed to their own types

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
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.crypto.symmetric.SymmetricCrypto;
import com.ruoyi.common.constant.EncryptConstant;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.encrypt.dto.ApiEncryptInfoDTO;
import com.ruoyi.common.utils.encrypt.dto.Result;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.stereotype.Service;

/**
 * Implementation class of encryption and decryption related functions
 *
 * @author gbx
 */
@Service
@Slf4j
public class ApiEncryptServiceImpl implements ApiEncryptService {

    /**
     * SM2 encryption
     *
     * @param dto Entity containing encryption and decryption related parameter information
     * @return Processing results
     */
    @Override
    public Result encrypt2Data(ApiEncryptInfoDTO dto) {
        String publicKey = dto.getPublicKey();
        // If it is blank, the default value is used
        if (StringUtils.isBlank(publicKey)) {
            publicKey = EncryptConstant.PUBLIC_KEY;
        }
        String data = dto.getData();
        //Create sm2 object
        SM2 sm2 = getSM2(null, publicKey);
        String dataHex = sm2.encryptBcd(data, KeyType.PublicKey);
        dto.setDataHex(dataHex);
        return new Result().ok(dto);
    }

    /**
     * SM2 decrypt
     *
     * @param dto Entity containing encryption and decryption related parameter information
     * @return Processing results
     */
    @Override
    public Result decrypt2Data(ApiEncryptInfoDTO dto) {
        String privateKey = dto.getPrivateKey();
        // If it is blank, the default value is used
        if (StringUtils.isBlank(privateKey)) {
            privateKey = EncryptConstant.PRIVATE_KEY;
        }
        String dataHex = dto.getDataHex();
        try {
            //Create sm2 object
            SM2 sm2 = getSM2(privateKey, null);
            String data = StrUtil.utf8Str(sm2.decryptFromBcd(dataHex, KeyType.PrivateKey));
            dto.setData(data);
        } catch (Exception e) {
            log.error("SM2 Decryption failed", e);
            throw new BaseException("SM2 Decryption failed");
        }
        return new Result().ok(dto);
    }

    /**
     * SM4 encryption
     *
     * @param dto Entity containing encryption and decryption related parameter information
     * @return Processing results
     */
    @Override
    public Result encrypt4Data(ApiEncryptInfoDTO dto) {
        //Specified key
        String key = dto.getKey();
        // If it is blank, the default value is used
        if (StringUtils.isBlank(key)) {
            key = EncryptConstant.SM4_KEY;
        }
        String data = dto.getData();
        try {
            SymmetricCrypto sm4 = SmUtil.sm4(key.getBytes(CharsetUtil.CHARSET_UTF_8));
            String dataHex = sm4.encryptHex(data);
            dto.setDataHex(dataHex);
        } catch (Exception e) {
            log.error("Encrypted data exception, exception data:" + data, e);
            throw new BaseException("SM4 Encrypted data exception");
        }
        return new Result().ok(dto);
    }

    /**
     * SM4 decrypt
     *
     * @param dto Entity containing encryption and decryption related parameter information
     * @return Processing results
     */
    @Override
    public Result decrypt4Data(ApiEncryptInfoDTO dto) {
        //Specified key
        String key = dto.getKey();
        // If it is blank, the default value is used
        if (StringUtils.isBlank(key)) {
            key = EncryptConstant.SM4_KEY;
        }
        String dataHex = dto.getDataHex();
        try {
            SymmetricCrypto sm4 = SmUtil.sm4(key.getBytes(CharsetUtil.CHARSET_UTF_8));
            String data = sm4.decryptStr(dataHex);
            dto.setData(data);
        } catch (Exception e) {
            log.error("Decryption data exception, exception data:" + dataHex, e);
            throw new BaseException("SM4 Decryption data exception");
        }
        return new Result().ok(dto);
    }

    /**
     * Generate a pair of SM2 keys in C1C2C3 format
     *
     * @return Processing results
     */
    @Override
    public Result getSM2Key() {
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        //Create sm2 object
        SM2 sm2 = SmUtil.sm2();
        byte[] privateKeyByte = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
        //Here, the public key is not compressed. The first byte of the public key is used to indicate whether compression can be avoided
        byte[] publicKeyByte = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
        try {
            String privateKey = HexUtil.encodeHexStr(privateKeyByte);
            String publicKey = HexUtil.encodeHexStr(publicKeyByte);
            dto.setPublicKey(publicKey);
            dto.setPrivateKey(privateKey);
        } catch (Exception e) {
            log.error("obtain SM2 Key error", e);
            throw new BaseException("obtain SM2 Key error");
        }
        return new Result().ok(dto);
    }

    /**
     * Get SM2 encryption tool object
     *
     * @param privateKey Encrypted private key
     * @param publicKey  Encryption public key
     * @return Processing results
     */
    private SM2 getSM2(String privateKey, String publicKey) {
        ECPrivateKeyParameters ecPrivateKeyParameters = null;
        ECPublicKeyParameters ecPublicKeyParameters = null;
        if (StringUtils.isNotBlank(privateKey)) {
            ecPrivateKeyParameters = BCUtil.toSm2Params(privateKey);
        }
        if (StringUtils.isNotBlank(publicKey)) {
            if (publicKey.length() == 130) {
                //Here, you need to remove the first byte and the first byte representation mark
                publicKey = publicKey.substring(2);
            }
            String xhex = publicKey.substring(0, 64);
            String yhex = publicKey.substring(64, 128);
            ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
        }
        //Create sm2 object
        SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters);
        sm2.usePlainEncoding();
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        return sm2;
    }

    /**
     * Get a random SM4 key
     *
     * @return Processing results
     */
    @Override
    public Result getSM4Key() {
        String sm4Key = RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER, 16);
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        dto.setKey(sm4Key);
        return new Result().ok(dto);
    }
}

Class for storing data information (lombok dependency is used here. If not, add get set)

package com.ruoyi.common.utils.encrypt.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;


/**
 * Data encryption information DTO
 *
 * @author gbx
 * @since 2021-05-27 10:55:32
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiEncryptInfoDTO implements Serializable {
    private static final long serialVersionUID = 693123123935846450L;

    /**
     * Encryption type (2:sm2 encryption, 4:sm4 encryption)
     */
    private String type;

    /**
     * Asymmetric encryption private key
     */
    private String privateKey;

    /**
     * Asymmetric encryption public key
     */
    private String publicKey;

    /**
     * Symmetric encryption key
     */
    private String key;

    /**
     * raw data
     */
    private String data;

    /**
     * Encrypted data
     */
    private String dataHex;

    /**
     * Asymmetric encryption signature
     */
    private String sign;
}

Tool class

import com.ruoyi.common.utils.encrypt.dto.ApiEncryptInfoDTO;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;

/**
 * Encryption and decryption tool class
 *
 * @author gbx
 */
public class EncryptUtils {

    /**
     * Decrypt data using SM2 key
     *
     * @param sm2DataHex Ciphertext data
     * @return Plaintext data
     */
    public static String getSm2Data(String sm2DataHex, ApiEncryptService encryptService, String privateKey) {
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        dto.setPrivateKey(privateKey);
        dto.setDataHex(sm2DataHex);
        dto.setType("2");
        encryptService.decrypt2Data(dto);
        return dto.getData();
    }

    /**
     * Encrypt data with SM2 key
     *
     * @param sm2Data Plaintext data
     * @return Ciphertext data
     */
    public static String getSm2DataHex(String sm2Data, ApiEncryptService encryptService, String publicKey) {
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        dto.setPublicKey(publicKey);
        dto.setData(sm2Data);
        dto.setType("2");
        encryptService.encrypt2Data(dto);
        return dto.getDataHex();
    }

    /**
     * Decrypt data using SM4 key
     *
     * @param sm4DataHex Ciphertext data
     * @return Plaintext data
     */
    public static String getSm4Data(String sm4DataHex, ApiEncryptService encryptService, String key) {
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        dto.setKey(key);
        dto.setDataHex(sm4DataHex);
        dto.setType("4");
        encryptService.decrypt4Data(dto);
        return dto.getData();
    }

    /**
     * Encrypt data with SM4 key
     *
     * @param sm4Data Plaintext data
     * @return Ciphertext data
     */
    public static String getSm4DataHex(String sm4Data, ApiEncryptService encryptService, String key) {
        ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
        dto.setKey(key);
        dto.setData(sm4Data);
        dto.setType("4");
        encryptService.encrypt4Data(dto);
        return dto.getDataHex();
    }
}

So far, the function interface is introduced to use the corresponding encryption and decryption method.

(3) Use sm2 to decrypt the request data and encrypt the response data for the interface specified by the springboot backend

ps: SM2 is an asymmetric algorithm. There is a pair of public and private keys. Call the previous function of generating SM2 key pair to generate two pairs of keys, one for back-end use and one for front-end use. Each party gives its own public key to the other party and allows the other party to use its own public key encryption when transmitting.

The back-end mainly implements the encryption and decryption of the front-end data by adding interceptors to intercept the specified path interface. Here, the front-end and back-end agree that all data (objects are converted into json strings) will be encrypted and transmitted with "data" as the key, such as:

{"data": "K3KD89ASK2JKN8923JNKJF"}

The interface path to intercept encryption and decryption here is configured in the yml file and loaded through the static constant initialization class. The specific methods may be explained later in the blog post, which is not covered here.

Specific interceptor settings
(a) Request interception and decryption

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.encrypt.utils.EncryptDefaultUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * Request parameter decryption processing
 *
 * @author gbx
 */
@Slf4j
public class DataEncryptionWrapper extends HttpServletRequestWrapper {
    private ApiEncryptService encryptService;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private String body = "";

    /**
     * Store param, formdata, body and other parameters to obtain the re definition of parameters
     */
    private Map params = new HashMap();

    /**
     * Key for unified encryption request parameters
     */
    private static final String REQ_BODY_KEY = "data";

    public DataEncryptionWrapper(HttpServletRequest request, ApiEncryptService encryptService) throws IOException {
        super(request);
        this.encryptService = encryptService;
        String contentType = request.getContentType();
        if (org.apache.commons.lang3.StringUtils.isNotBlank(contentType)
                && (org.springframework.util.StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
        ) {
            Map parametersMap = request.getParameterMap();
            if (parametersMap.containsKey(REQ_BODY_KEY)) {
                String data = ((String[]) parametersMap.get(REQ_BODY_KEY))[0];
                String deJson = EncryptDefaultUtils.getSm2Data(data, encryptService);
                this.params.putAll(objectMapper.readValue(deJson, Map.class));
            }
            //Put in other form s
            for (Object key : parametersMap.keySet()) {
                if (!key.equals(REQ_BODY_KEY)) {
                    this.params.put(key, parametersMap.get(key));
                }
            }
        } else if (org.apache.commons.lang3.StringUtils.isNotBlank(contentType)
                && org.springframework.util.StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_JSON_VALUE)) {
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader bufferedReader = null;
            try {
                InputStream inputStream = request.getInputStream();
                if (inputStream != null) {
                    bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
                    char[] charBuffer = new char[128];
                    int bytesRead = -1;
                    while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                        stringBuilder.append(charBuffer, 0, bytesRead);
                    }
                }
            } catch (IOException ex) {
                throw ex;
            } finally {
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close();
                    } catch (IOException ex) {
                        throw ex;
                    }
                }
            }

            String requestURI = request.getRequestURI();
            //Get request parameters
            String queryString = request.getQueryString();
            log.info("====> Request path:" + requestURI + ",Original request query parameter:{}", queryString);

            String bodyStr = stringBuilder.toString();
            bodyStr = bodyStr.startsWith("[") && bodyStr.endsWith("]") ? bodyStr.substring(1, bodyStr.length() - 1) : bodyStr;
            if (StringUtils.isNotBlank(bodyStr)) {
                JSONObject jsonObject = JSONObject.parseObject(bodyStr);
                if (jsonObject != null) {
                    //Get request body
                    log.info("====> Request path:" + requestURI + ",Original request body Parameter body:{}", JSONObject.toJSONString(jsonObject, SerializerFeature.WriteMapNullValue));
                    String enJson = jsonObject.getString(REQ_BODY_KEY);
                    if (org.apache.commons.lang3.StringUtils.isNotBlank(enJson)) {
                        String deJson = EncryptDefaultUtils.getSm2Data(enJson, encryptService);
                        body = deJson.startsWith("[") && deJson.endsWith("]") ? deJson.substring(1, deJson.length() - 1) : deJson;
                        this.params.putAll(objectMapper.readValue(body, Map.class));
                    }
                }
            }
        }

        // RequestDispatcher.forward parameter
        renewParameterMap(request);
    }

    @Override
    public String getParameter(String name) {
        String result = "";

        Object v = params.get(name);
        if (v == null && StringUtils.isNotBlank(body)) {
            JSONObject jsonObject = JSONObject.parseObject(body);
            v = jsonObject.get(name);
        }
        if (v == null) {
            result = null;
        } else if (v instanceof String[]) {
            String[] strArr = (String[]) v;
            if (strArr.length > 0) {
                try {
                    result = URLDecoder.decode(strArr[0], "utf-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            } else {
                result = null;
            }
        } else if (v instanceof String) {
            result = (String) v;
            try {
                result = URLDecoder.decode(result, "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else {
            result = v.toString();
        }
        return result;
    }

    @Override
    public Map getParameterMap() {
        return params;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return new Vector<String>(params.keySet()).elements();
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] result = null;
        Object v = params.get(name);
        if (v == null && StringUtils.isNotBlank(body)) {
            JSONObject jsonObject = JSONObject.parseObject(body);
            v = jsonObject.get(name);
        }
        if (v == null) {
            result = null;
        } else if (v instanceof String[]) {
            result = (String[]) v;
            for (int i = 0; i < result.length; i++) {
                try {
                    result[i] = URLDecoder.decode(result[i], "utf-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                //Adding and decrypting param parameters
                if (StringUtils.isNotBlank(result[i])) {
                    try {
                        result[i] = EncryptDefaultUtils.getSm2Data(result[i], encryptService);
                    } catch (Exception e) {
                        throw new BaseException("decrypt param Request parameters failed");
                    }
                }
            }
        } else if (v instanceof String) {
            try {
                result = new String[]{URLDecoder.decode((String) v, "utf-8")};
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            //Adding and decrypting param parameters
            if (StringUtils.isNotBlank(result[0])) {
                try {
                    result[0] = EncryptDefaultUtils.getSm2Data(result[0], encryptService);
                } catch (Exception e) {
                    throw new BaseException("decrypt param Request parameters failed");
                }
            }
        } else {
            try {
                result = new String[]{URLDecoder.decode(v.toString(), "utf-8")};
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            //Adding and decrypting param parameters
            if (StringUtils.isNotBlank(result[0])) {
                try {
                    result[0] = EncryptDefaultUtils.getSm2Data(result[0], encryptService);
                } catch (Exception e) {
                    throw new BaseException("decrypt param Request parameters failed");
                }
            }
        }
        return result;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream(), "UTF-8"));
    }

    private void renewParameterMap(HttpServletRequest req) {

        String queryString = req.getQueryString();

        if (queryString != null && queryString.trim().length() > 0) {
            String[] params = queryString.split("&");

            for (int i = 0; i < params.length; i++) {
                int splitIndex = params[i].indexOf("=");
                if (splitIndex == -1) {
                    continue;
                }

                String key = params[i].substring(0, splitIndex);

                if (!this.params.containsKey(key)) {
                    if (splitIndex < params[i].length()) {
                        String value = params[i].substring(splitIndex + 1);
                        try {
                            this.params.put(key, new String[]{URLDecoder.decode(value, "utf-8")});
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}




import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.interceptor.EncryptInterceptorUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
 * Customize dispatcher servlet to dispatch GbxHttpServletRequestWrapper
 *
 * @author gbx
 * @date 2021-08-30 14:25
 */
public class GbxDispatcherServlet extends DispatcherServlet {

    /**
     * Encryption function service interface class
     */
    private ApiEncryptService encryptService;

    public GbxDispatcherServlet() {
    }

    public GbxDispatcherServlet(ApiEncryptService encryptService) {
        this.encryptService = encryptService;
    }

    /**
     * Wrap custom request
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String requestURI = request.getRequestURI();
        // Decrypt the specified request path interface that meets the requirements
        if (StringUtils.isNotBlank(requestURI) && EncryptInterceptorUtils.checkApi(requestURI)) {
            super.doDispatch(new DataEncryptionWrapper(request, encryptService), response);
        } else {
            super.doDispatch(new HttpServletRequestWrapper(request), response);
        }

    }
}




import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.framework.interceptor.request.GbxDispatcherServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * WebMVC Configuration, centralized configuration of interceptors, filters and static resource cache
 *
 * @author gbx
 * @date 2021-08-30 10:15
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ApiEncryptService encryptService;

    /**
     * Use custom request forwarding
     *
     * @return
     */
    @Bean
    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        return new GbxDispatcherServlet(encryptService);
    }
}

So far, when the data through this interceptor reaches the Controller Api interface, it is already the normal decrypted data. You can log print the parameters in the relevant interface to view the specific parameters.

(b) Response interception encryption

The encryption of response parameters is relatively simple. Because the unified return parameter body is used and returned in the body mode, a return body parameter interception interface provided by Springboot can be implemented for operation

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.constant.EncryptConstant;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.encrypt.utils.EncryptUtils;
import com.ruoyi.common.utils.interceptor.EncryptInterceptorUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * response Response parameter unified encryption processing interceptor
 *
 * @author gbx
 */
@ControllerAdvice
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ApiEncryptService encryptService;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        Map<String, Object> resMap = new HashMap<>(16);
        try {
            String path = serverHttpRequest.getURI().getPath();
            if (StringUtils.isNotBlank(path) && EncryptInterceptorUtils.checkApi(path)) {
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(body);
                log.info("====> Response path:" + path + ",Original actual return parameters:" + json);
                if (StringUtils.isNotBlank(json)) {
                    String dataHex = EncryptUtils.getSm2DataHex(json, encryptService, EncryptConstant.UI_PUBLIC_KEY);
                    resMap.put("data", dataHex);
                }
            }
            log.info("====> Response path:" + path + ",Actual response parameters:{}", JSONObject.toJSONString(resMap, SerializerFeature.WriteMapNullValue));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        if (ObjectUtils.isNotEmpty(resMap)) {
            return resMap;
        }
        return body;
    }
}

At this point, the processing of response parameter encryption return is completed.

For front-end transmission, the objects are uniformly converted into json strings, the corresponding "data" is key transmission, and the param parameter is encrypted separately, such as:

/login?validateCode=3JKD3NJKNDKJ3

Front end (Vue)

The reason why we put the front and rear ends together is that many settings of the front and rear ends are variable and must be matched and unified in order to interact normally. For example, the encryption strategy. If C1C2C3 is used at the back end, the corresponding strategy must also be used at the front end. In addition, the encryption and decryption tool classes at the front and rear ends need differential adjustment because they are different from the default key headers.

Front end import dependency:

npm install --save sm-crypto

Because the front-end vue basically uses a unified request JS, it is relatively simple to process the encryption and decryption of relevant parameters in the interceptor inside. It will not be repeated here. Only the front-end encryption and decryption methods that are prone to problems are encapsulated and displayed

Front end unified encryption and decryption tool class:

const sm2 = require('sm-crypto').sm2 // Get sm2 object
const cipherMode = 0 // Select the encryption policy, 1 - C1C3C2, 0 - C1C2C3. The default is 1
const sysPublicKey = 'Your corresponding background public key' // System background public key
const uiPrivateKey = 'Your own private key' // Front end UI private key

/**
 * SM2 Encrypt string data
 * @param {string} data raw data
 * @returns {string} Encrypted data
 */
export function getSm2DataHexByString(data) {
  if (data && (typeof data === 'string') && data.constructor === String) {
    return '04' + sm2.doEncrypt(data, sysPublicKey, cipherMode)
  }
  return null
}

/**
 * SM2 Encrypt object data
 * @param {Object} data raw data
 * @returns {string} Encrypted data
 */
export function getSm2DataHexByObject(data) {
  if (data) {
    return '04' + sm2.doEncrypt(JSON.stringify(data), sysPublicKey, cipherMode)
  }
  return null
}

/**
 * SM2 Decrypt data
 * @param {string} dataHex Original encrypted data
 * @returns {string} Decrypted data
 */
export function getSm2DataByString(dataHex) {
  if (dataHex && (typeof dataHex === 'string') && dataHex.constructor === String) {
    dataHex = dataHex.substring(2).toLocaleLowerCase()
    return sm2.doDecrypt(dataHex, uiPrivateKey, cipherMode)
  }
}

So far, the data encryption transmission of the front and rear end state secret SM2 has been completed, and most of the dependencies are general dependencies. If there is any dependence on red reporting, then search where it is. After all, we are programming for Baidu (manual funny)!

Keywords: Javascript Front-end JSON TypeScript html

Added by mmorton on Thu, 03 Mar 2022 19:14:26 +0200