Springboot 数据安全传输加密与解密

开发 前端
通过继承RequestBodyAdviceAdapter实现对于请求的内容进行解密操作,实现ResponseBodyAdvice来对相应内容进行加密处理。

[[374394]]

 环境:springboot2.2.6.RELEASE、Vue+axios

通过继承RequestBodyAdviceAdapter实现对于请求的内容进行解密操作,实现ResponseBodyAdvice来对相应内容进行加密处理。

定义加密解密的接口:

SecretProcess.java

public interface SecretProcess { 
     
    /** 
     *  <p>数据加密</p> 
     *  <p>时间:2020年12月24日-下午12:22:13</p> 
     * @author xg 
     * @param data 待加密数据 
     * @return String 加密结果 
     */ 
    String encrypt(String data) ; 
     
    /** 
     *  <p>数据解密</p> 
     *  <p>时间:2020年12月24日-下午12:23:20</p> 
     * @author xg 
     * @param data 待解密数据 
     * @return String 解密后的数据 
     */ 
    String decrypt(String data) ; 
     
    /** 
     *  <p>加密算法格式:算法[/模式/填充]</p> 
     *  <p>时间:2020年12月24日-下午12:32:49</p> 
     * @author xg 
     * @return String 
     */ 
    String getAlgorithm() ; 
     
    public static class Hex { 
         
        private static final char[] HEX = { '0''1''2''3''4''5''6''7''8''9'
                'a''b''c''d''e''f' }; 
         
        public static byte[] decode(CharSequence s) { 
            int nChars = s.length(); 
            if (nChars % 2 != 0) { 
                throw new IllegalArgumentException("16进制数据错误"); 
            } 
            byte[] result = new byte[nChars / 2]; 
            for (int i = 0; i < nChars; i += 2) { 
                int msb = Character.digit(s.charAt(i), 16); 
                int lsb = Character.digit(s.charAt(i + 1), 16); 
                if (msb < 0 || lsb < 0) { 
                    throw new IllegalArgumentException( 
                        "Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position"); 
                } 
                result[i / 2] = (byte) ((msb << 4) | lsb); 
            } 
            return result; 
        } 
         
        public static String encode(byte[] buf) { 
            StringBuilder sb = new StringBuilder() ; 
            for (int i = 0, leng = buf.length; i < leng; i++) { 
                sb.append(HEX[(buf[i] & 0xF0) >>> 4]).append(HEX[buf[i] & 0x0F]) ; 
            } 
            return sb.toString() ; 
        } 
         
    } 
     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.

 该接口中定义了两个方法分别是加密与解密的方法,还有Hex类 该类用来对数据处理16进制的转换。

定义一个抽象类实现上面的接口,具体的加解密实现细节在该抽象类中

AbstractSecretProcess.java

public abstract class AbstractSecretProcess implements SecretProcess { 
     
    @Resource 
    private SecretProperties props ; 
     
    @Override 
    public String decrypt(String data) { 
        try { 
            Cipher cipher = Cipher.getInstance(getAlgorithm()) ; 
            cipher.init(Cipher.DECRYPT_MODE, keySpec()) ; 
            byte[] decryptBytes = cipher.doFinal(Hex.decode(data)) ; 
            return new String(decryptBytes) ; 
        } catch (Exception e) { 
            throw new RuntimeException(e) ; 
        } 
    } 
     
    @Override 
    public String encrypt(String data) { 
        try { 
            Cipher cipher = Cipher.getInstance(getAlgorithm()) ; 
            cipher.init(Cipher.ENCRYPT_MODE, keySpec()) ; 
            return Hex.encode(cipher.doFinal(data.getBytes(Charset.forName("UTF-8")))) ; 
        } catch (Exception e) { 
            throw new RuntimeException(e) ; 
        } 
    } 
     
    /** 
     *  <p>根据密钥生成不同的密钥材料</p> 
     *  <p>目前支持:AES, DES</p> 
     *  <p>时间:2020年12月25日-下午1:02:54</p> 
     * @author xg 
     * @param secretKey 密钥 
     * @param algorithm 算法 
     * @return Key 
     */ 
    public Key getKeySpec(String algorithm) { 
        if (algorithm == null || algorithm.trim().length() == 0) { 
            return null ; 
        } 
        String secretKey = props.getKey() ; 
        switch (algorithm.toUpperCase()) { 
            case "AES"
                return new SecretKeySpec(secretKey.getBytes(), "AES") ; 
            case "DES"
                Key key = null ; 
                try { 
                    DESKeySpec desKeySpec = new DESKeySpec(secretKey.getBytes()) ; 
                    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES") ; 
                    key = secretKeyFactory.generateSecret(desKeySpec); 
                } catch (Exception e) { 
                    throw new RuntimeException(e) ; 
                } 
                return key ; 
            default
                return null ; 
        } 
    } 
     
    /** 
     *  <p>生成密钥材料</p> 
     *  <p>时间:2020年12月25日-上午11:35:03</p> 
     * @author xg 
     * @return Key 密钥材料 
     */ 
    public abstract Key keySpec() ; 
     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.

 该抽象类中提供了2中对称加密的密钥还原,分表是AES和DES算法。一个抽象方法,该抽象方法

keySpec该方法需要子类实现(具体使用的是哪种对称加密算法)。

具体加密算法的实现类

AESAlgorithm.java

public class AESAlgorithm extends AbstractSecretProcess { 
 
    @Override 
    public String getAlgorithm() { 
        return "AES/ECB/PKCS5Padding"
    } 
     
    @Override 
    public Key keySpec() { 
        return this.getKeySpec("AES") ; 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

 SecretProperties.java 属性配置类

@Configuration 
public class SecretConfig { 
     
    @Bean 
    @ConditionalOnMissingBean(SecretProcess.class) 
    public SecretProcess secretProcess() { 
        return new AESAlgorithm() ; 
    } 
     
    @Component 
    @ConfigurationProperties(prefix = "secret"
    public static class SecretProperties { 
         
        private Boolean enabled ; 
        private String key ; 
 
        public Boolean getEnabled() { 
            return enabled; 
        } 
 
        public void setEnabled(Boolean enabled) { 
            this.enabled = enabled; 
        } 
 
        public String getKey() { 
            return key
        } 
 
        public void setKey(String key) { 
            this.key = key
        } 
         
    } 
     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

 配置文件中如下配置:

secret: 
  key: aaaabbbbccccdddd #密钥 
  enabled: true #是否开启加解密功能 
  • 1.
  • 2.
  • 3.

 在项目中可能不是所有的方法都要进行数据的加密解密出来,所以接下来定义一个注解,只有添加有该注解的Controller类或是具体接口方法才进行数据的加密解密,如下:

SIProtection.java

@Target({ElementType.METHOD, ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@Mapping 
@Documented 
public @interface SIProtection { 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 对请求内容进行解密出来,通过RequestBodyAdvice

DecryptRequestBodyAdivce.java

@ControllerAdvice 
@ConditionalOnProperty(name = "secret.enabled", havingValue = "true"
public class DecryptRequestBodyAdivce extends RequestBodyAdviceAdapter { 
 
    @Resource 
    private SecretProcess secretProcess ; 
     
    @Override 
    public boolean supports(MethodParameter methodParameter, Type targetType, 
            Class<? extends HttpMessageConverter<?>> converterType) { 
        return methodParameter.getMethod().isAnnotationPresent(SIProtection.class)  
                || methodParameter.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class) ; 
    } 
 
    @Override 
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, 
            Class<? extends HttpMessageConverter<?>> converterType) throws IOException { 
        String body = secretProcess.decrypt(inToString(inputMessage.getBody())) ; 
        return new HttpInputMessage() { 
            @Override 
            public HttpHeaders getHeaders() { 
                return inputMessage.getHeaders(); 
            } 
            @Override 
            public InputStream getBody() throws IOException { 
                return new ByteArrayInputStream(body.getBytes()) ; 
            } 
        } ; 
    } 
     
    private String inToString(InputStream is) { 
        byte[] buf = new byte[10 * 1024] ; 
        int leng = -1 ; 
        StringBuilder sb = new StringBuilder() ; 
        try { 
            while ((leng = is.read(buf)) != -1) { 
                sb.append(new String(buf, 0, leng)) ; 
            } 
            return sb.toString() ; 
        } catch (IOException e) { 
            throw new RuntimeException(e) ; 
        } 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

 注意这里的:@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")注解,只有开启了加解密功能才会生效。注意这里的supports方法

对响应内容加密出来

EncryptResponseBodyAdivce.java

@ControllerAdvice 
@ConditionalOnProperty(name = "secret.enabled", havingValue = "true"
public class EncryptResponseBodyAdivce implements ResponseBodyAdvice<Object>  { 
 
    @Resource 
    private SecretProcess secretProcess ; 
 
    @Override 
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { 
        return returnType.getMethod().isAnnotationPresent(SIProtection.class)  
                || returnType.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class) ; 
    } 
 
    @Override 
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, 
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, 
            ServerHttpResponse response) { 
        if (body == null) { 
            return body ; 
        } 
        try { 
            String jsonStr = new ObjectMapper().writeValueAsString(body) ; 
            return secretProcess.encrypt(jsonStr) ; 
        } catch (Exception e) { 
            throw new RuntimeException(e) ; 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

 Controller应用

@PostMapping("/save"
    @SIProtection 
    public R save(@RequestBody Users users) { 
        return R.success(usersService.save(users)) ; 
    } // 这对具体方法进行加解密 
 
@RestController 
@RequestMapping("/users"
@SIProtection  
public class UsersController { // 对该Controller中的所有方法进行加解密处理 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

 前端

引入第三方插件:crypto-js

工具方法加解密:

/** 
     * 加密方法 
     * @param data 待加密数据 
     * @returns {string|*} 
     */ 
    encrypt (data) { 
      let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key
      if (typeof data === 'object') { 
        data = JSON.stringify(data) 
      } 
      let plainText = CryptoJS.enc.Utf8.parse(data) 
      let secretText = CryptoJS.AES.encrypt(plainText, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).ciphertext.toString() 
      return secretText 
    }, 
    /** 
     * 解密数据 
     * @param data 待解密数据 
     */ 
    decrypt (data) { 
      let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key
      let secretText = CryptoJS.enc.Hex.parse(data) 
      let encryptedBase64Str = CryptoJS.enc.Base64.stringify(secretText) 
      let result = CryptoJS.AES.decrypt(encryptedBase64Str, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8) 
      return JSON.parse(result) 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

 配置:

let Consts = { 
  Secret: { 
    key'aaaabbbbccccdddd', // 必须16位(前后端要一致,密钥) 
    urls: ['/users/save'
  } 

export default Consts 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 这里的urls表示对那些请求进行拦截出来(加解密),这里也可以配置 "*" 表示对所有的请求出来。

axios请求前和响应后对数据进行加解密出来:

发送请求前:

axios.interceptors.request.use((config) => { 
      let uri = config.url 
      if (uri.includes('?')) { 
        uri = uri.substring(0, uri.indexOf('?')) 
      } 
      if (window.cfg.enableSecret === '1' && config.data && (Consts.Secret.urls.indexOf('*') > -1 || Consts.Secret.urls.indexOf(uri) > -1)) { 
        let data = config.data 
        let secretText = Utils.Secret.encrypt(data) 
        config.data = secretText 
      } 
      return config 
    }, (error) => { 
      let errorMessage = '请求失败' 
      store.dispatch(types.G_SHOW_ALERT, {title: '请求失败', content: errorMessage, showDetail: false, detailContent: String(error)}) 
      return Promise.reject(error) 
    }) 
axios.interceptors.response.use((response) => { 
      let uri = response.config.url 
      if (uri.includes('?')) { 
        uri = uri.substring(0, uri.indexOf('?')) 
      } 
      if (window.cfg.enableSecret === '1' && response.data && (Consts.Secret.urls.indexOf('*') > -1 || Consts.Secret.urls.indexOf(uri) > -1)) { 
        let data = Utils.Secret.decrypt(response.data) 
        if (data) { 
          response.data = data 
        } 
      } 
      return response 
    }, (error) => { 
      console.error(`test interceptors.response is in, ${error}`) 
      return Promise.reject(error) 
    }) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

 这里的 window.cfg.enableSecret 配置是我自己项目中有个配置文件配置是否开启,这个大家可以根据自己的环境来实现。

测试:


这里可以看到前端发起的请求内容已经被加密了

响应内容:


完毕!!!

 

责任编辑:姜华 来源: 今日头条
相关推荐

2011-08-01 10:36:01

2024-06-13 08:41:41

2021-01-29 08:19:50

HTTPS安全传输

2020-09-26 22:04:32

数据安全传输HTTPSHTTP 协议

2024-10-15 10:38:32

2024-07-09 10:13:15

2013-03-21 09:32:31

文件传输安全文件传输

2024-05-08 08:16:11

2016-10-10 23:00:18

2009-11-26 13:12:01

2016-10-10 22:48:16

2023-03-06 08:49:02

加密和解密SpringBoot

2022-10-28 18:36:18

2025-03-26 08:43:17

2017-08-14 15:14:33

2015-03-11 17:06:34

SDH网络评估优化服务华为

2019-12-13 10:42:03

LinuxSCP命令

2010-04-23 14:33:34

邮件服务器加密

2024-12-31 08:54:38

2010-01-12 12:26:58

数据备份云安全数据销毁
点赞
收藏

51CTO技术栈公众号