SpringBoot接口加密与解密
文章目录
- 一、对称/非对称加密
- 1、简介
- 2、RSA和AES介绍
- 2.1 RSA
- 2.2 AES
- 3、RSA/AES组合
- 4、Base64编码的作用
- 二、Java实现加解密/加验签
- 1、全局Config
- 2、RSA非对称加密
- 3、AES对称加密
- 三、加解密 starter实战
- 1、介绍
- 2、前期准备
- 2.1 引入依赖
- 2.2 封装公共相应类
- 2.3 定义加解密工具类
- 2.4 定义两个注解
- 2.5 设置自定义key
- 3、接口加密与解密
- 3.1 介绍
- 3.2 接口加密
- 3.3 接口解密
- 4、打包发布starter
- 4.1 定义自动化配置类
- 4.2 发布线上使用
- 5、新项目使用
一、对称/非对称加密
1、简介
对称加密只有一个秘钥,加密和解密都是用同一个秘钥,所以叫做对称加密。
非对称加密有两个秘钥,一个是公钥,一个是私钥。非对称的特点在于,公钥加密的私钥可以解密,但私钥加密的,公钥解不出来,只能验证是否由私钥进行加密
目前常见的加密方式是有两种,一种是对称加密(AES为代表),一种是非对称加密(RSA为代表)
2、RSA和AES介绍
2.1 RSA
特点:只需交换公钥;公/秘钥机制,公钥加密,私钥解密(或者私钥加密,公钥解密);公钥负责加密,私钥负责解密;私钥负责签名,公钥负责验证
缺点:加解密速度慢,特别是解密
2.2 AES
特点:加解密用同一秘钥
优点:速度快,效率高;
缺点:秘钥交换问题
3、RSA/AES组合
对称加密(AES)的优势在于加密较快,但劣势在于秘钥一旦给出去就不安全了。非对称加密(RSA)的优势在于安全,就算提供公钥出去,别人也解密不了数据,但劣势是加密速度较慢
实际使用的过程中常常将两者组合使用(AES+RSA),这样可以安全的传输AES秘钥,避免了RSA加密的慢速度
生成一个随机AES秘钥字符串
使用RSA公钥加密AES秘钥,然后再用AES秘钥加密真正的内容
把skey=加密的AES秘钥,body=AES秘钥加密的内容传过去
对面使用RSA私钥解密AES秘钥,然后用AES秘钥解密出内容
4、Base64编码的作用
加密后的数据可能不具备可读性,因此我们一般需要对加密后的数据再使用 Base64 算法进行编码,获取可读字符串。换言之,AES 或者RSA加密方法的返回值是一个 Base64 编码之后的字符串,AES或者RSA 解密方法的参数也是一个 Base64 编码之后的字符串,先对该字符串进行解码,然后再解密。
二、Java实现加解密/加验签
1、全局Config
public class Config {public static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";//必须是PKCS8格式public static final String CLIENT_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO/8ucCgOTJ7DCPC" +"rCCL1VKDnUX61QnxwbAvpGp1/lletEIcjUouM7F0VvMHzViNLvpw7N7NBHPa+5gO" +"js68t9hKMUh+a6RTE34SWIqSDRPCzDKVWugsFb04o3vRl3rZ1z6B+QDdW7xwOhEr" +"PPoEqmjjIOjQPcU6xs0SPzSimOa1AgMBAAECgYAO5m0OBaSnerZNPhf7yVLMVbmd" +"D67MeEMjUkHuDjdlixi8BhPLqESzXtrLKg/Y0KM7D2nVh3sgSldWoIjDUzpCx8Z2" +"yHLU1K2wakMdBgEF3xeJPxxZRpP+earl0SyLTA4hMxl48uAjn/mkPgzoMgQkqyQz" +"5HOWjjsCLJFyEvqmoQJBAP5cBk0KXpHnCMgOupbi/pXDyaF1o+dCE97GaEdrV/0P" +"uwDfYDYfY3wzd1QM7C4b4MmE+SNVpC0W9PyaMONJlN0CQQDxiPiGdwX9actMNJea" +"JZ+k3BjCN+mM6Px7j/mtYcXWNZkyCXSXUBI62drZ0htenrh2qwichMlMgNJClvG6" +"Gu+5AkEA30R7q2gstrkrNh/nnMZHXcJr3DPc2QNhWayin/4TT+hc51krpJZMxxqN" +"5dMqBRcnavwzi9aCs6lxBcF6pCdUaQJANhd7uPls4PzRZ6abkQz9/LjB3rUQ29rN" +"uIpc2yR7XuawAVG2x7BJ9N4XMhLoyD75hrH1AsCGKFjtPbZ6OjiQGQJAF2DbIodC" +"uYb6eMZ8ux1Ab0wBEWWc5+iGgEVBNh22uZ/klE1/C0+KKzZhqgzaA/vPapq6dhuJ" +"sNXlJia10PwYrQ==";public static final String CLIENT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv/LnAoDkyewwjwqwgi9VSg51F" +"+tUJ8cGwL6Rqdf5ZXrRCHI1KLjOxdFbzB81YjS76cOzezQRz2vuYDo7OvLfYSjFI" +"fmukUxN+EliKkg0TwswylVroLBW9OKN70Zd62dc+gfkA3Vu8cDoRKzz6BKpo4yDo" +"0D3FOsbNEj80opjmtQIDAQAB";public static final String SERVER_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPGkxlAJPKR3BRxT" +"PIeB3pDv117j8XbpuEik5UIOlY3GUtAV1sad5NNDUAnP/DB80yAQ8ycm9Xdkutuo" +"f25Xlb7w0bRQNpfJlijx9eF8PsB6t63r8KAfWJlqbNHgN8AMK9P5XzVyN4YiEnUl" +"Jh/EYiwLiYzflNnmnnfRrI4nUo8fAgMBAAECgYEAvwTxm81heeV4Tcbi33/jUBG4" +"4BMzCzyA6DQp4wkiYju3tTS+Xq3seLEKcWdPxYi3YO7lODsM6j/fksrlSXXFMe1i" +"ZAF3FNuDVZPz2zdFYS8vh6kdlDHMJAUnU/POMMWJ880MQDtkwTuzH8Tao8OKcAP4" +"kc0QuG00wOrmuE+5gZECQQD9bqZkJsN+tj3+pxs57azy6B6gOqgm54/ujB+u63XU" +"rO9Sf57asgF4OfUFltaVhjlUMSrWcgp6f4HSy7hBSKJpAkEA9BeML5iDIHOgTIws" +"+ID55ELbzO7A/YtcYnUU09mkKCdonMXbXke+EhLApf5vX9ZmreoEfJCdsTnMEcQi" +"fkjkRwJBALpf2TXl2/cfhs/zjG45f+rTEVK8UFTsDklb+yDkQC87TnTZLbWfGr2T" +"wcFugDhOEXL9BYfXLiWQB6VB9Crug6ECQGEmTiFTbj0oSBCvaeauTsdO5PS3whAn" +"u2lkeBmpcfCZXsWm6hyoKTpARHTMw789Mjjd/1Mkq96xxkr76U6h7FkCQHRc2elg" +"Dh84wqHIptwa+moosVvd7aSzktuOB4CQRO10qKkSHVFuI+sl47A4KGzH/nX9ydUm" +"tpsTnQAlXwBczd4=";public static final String SERVER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxpMZQCTykdwUcUzyHgd6Q79de" +"4/F26bhIpOVCDpWNxlLQFdbGneTTQ1AJz/wwfNMgEPMnJvV3ZLrbqH9uV5W+8NG0" +"UDaXyZYo8fXhfD7Aeret6/CgH1iZamzR4DfADCvT+V81cjeGIhJ1JSYfxGIsC4mM" +"35TZ5p530ayOJ1KPHwIDAQAB";}
2、RSA非对称加密
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.*;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.springframework.util.Base64Utils;public class RSACipher {/*** 获取公钥* @param key 密钥字符串(经过base64编码)* @return 公钥*/public static PublicKey getPublicKey(String key) throws Exception {// 按照X.509标准对其进行编码的密钥X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Utils.decode(key.getBytes()));KeyFactory keyFactory = KeyFactory.getInstance("RSA");// 生成公钥PublicKey publicKey = keyFactory.generatePublic(keySpec);return publicKey;}/*** 获取私钥* @param key 密钥字符串(经过base64编码)* @return 私钥*/public static PrivateKey getPrivateKey(String key) throws Exception {// 按照PKCS8格式标准对其进行编码的密钥,首先要将key进行base64解码PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Utils.decode(key.getBytes()));KeyFactory keyFactory = KeyFactory.getInstance("RSA");// 生成私钥PrivateKey privateKey = keyFactory.generatePrivate(keySpec);return privateKey;}/*** 加密方法* @param publicKey 公钥* @param raw 待加密明文* @return 加密后的密文*/public static byte[] encrypt(String publicKey, byte[] raw) throws Exception {Key key = getPublicKey(publicKey);Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM);// 初始化cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));byte[] encryption = cipher.doFinal(raw);// 最后将加密后的数据进行base64编码return Base64Utils.encode(encryption);}/*** 解密方法* @param privateKey 私钥* @param enc 待解密密文* @return 解密后的明文*/public static byte[] decrypt(String privateKey, byte[] enc) throws Exception {Key key = getPrivateKey(privateKey);Cipher cipher = Cipher.getInstance(Config.RSA_ALGORITHM);// 初始化cipher.init(Cipher.DECRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));// 先进行base64解密,然后解码return cipher.doFinal(Base64Utils.decode(enc));}/*** 签名* @param privateKey 私钥* @param content 要进行签名的内容* @return 签名*/public static String sign(String privateKey, byte[] content) {try {// privateKey进行base64编码,然后生成PKCS8格式私钥PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Utils.decode(privateKey.getBytes()));KeyFactory key = KeyFactory.getInstance("RSA");PrivateKey priKey = key.generatePrivate(priPKCS8);// 签名摘要算法Signature signature = Signature.getInstance("SHA256WithRSA");// 用私钥初始化此对象以进行签名signature.initSign(priKey);// 使用指定的字节数组更新签名或验证signature.update(content);// 获得签名字节byte[] signed = signature.sign();// 进行base64编码返回return new String(Base64Utils.encode(signed));} catch (Exception e) {e.printStackTrace();}return null;}/*** 验签* @param publicKey 公钥* @param content 要验签的内容* @param sign 签名* @return 验签结果*/public static boolean checkSign(String publicKey, byte[] content, String sign) {try {KeyFactory keyFactory = KeyFactory.getInstance("RSA");// 进行base64解码byte[] encodedKey = Base64Utils.decodeFromString(publicKey);// 生成公钥PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));// 签名摘要算法Signature signature = Signature.getInstance("SHA256WithRSA");// 用公钥初始化签名signature.initVerify(pubKey);// 使用指定的字节数组更新签名或验证signature.update(content);// base64解码后进行验证return signature.verify(Base64Utils.decodeFromString(sign));} catch (Exception e) {e.printStackTrace();}return false;}public static void main(String[] args) throws Exception {//客户端代码String text = "hello";//使用服务端公钥加密byte[] encryptText = RSACipher.encrypt(Config.SERVER_PUBLIC_KEY, text.getBytes());System.out.println("加密后:\n" + new String(encryptText));//使用客户端私钥签名String signature = RSACipher.sign(Config.CLIENT_PRIVATE_KEY, encryptText);System.out.println("签名:\n" + signature);//服务端代码//使用客户端公钥验签boolean result = RSACipher.checkSign(Config.CLIENT_PUBLIC_KEY, encryptText, signature);System.out.println("验签:\n" + result);//使用服务端私钥解密byte[] decryptText = RSACipher.decrypt(Config.SERVER_PRIVATE_KEY, encryptText);System.out.println("解密后:\n" + new String(decryptText));}
}
输出结果
加密后:
ODdEkwo1RgRW8UMoHXPKe9Gwcp6lTCkg4P/Ra3gfkrO+Fw6pSgo0H54nMC5sYSsoUVy1wy2/QXeLSwR6Obfl7SU7DeW+XdGee83O2kgdsDQPbYFwlPYTd0cdOmWwZxtgEOIB9d5G75Iut4kci15vrhXZVtku92U+7aNwtYimSDQ=
签名:
RL1qIScizRyu79/y+r2TN2FL/bSQDxnDj4JlDwSZM6XZR7CL7u5ZjLNHbsSYpHaCv9qKMS4ump50LyF+go05dsPjWZOvFNkgcm9LepkDP1qm8AzKdTGwlzhdBmy2397Ed8uBrQocFGj/721Y2xM/Db0nt7r54zKZkDXbMMlsd9k=
验签:
true
解密后:
hello
3、AES对称加密
import org.springframework.util.Base64Utils;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;public class AESCipher {public static SecureRandom random = new SecureRandom();/*** 获取随机16位key,key必须要是10的整数倍,否则会出错*/public static String getRandom(int length) {StringBuilder ret = new StringBuilder();for (int i = 0; i < length; i++) {// 输出字母还是数字boolean isChar = (random.nextInt(2) % 2 == 0);// 字符串if (isChar) {// 取得大写字母还是小写字母int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;ret.append((char) (choice + random.nextInt(26)));} else { // 数字ret.append(random.nextInt(10));}}return ret.toString();}/*** 加密方法,使用key充当向量iv,增加加密算法的强度* 更加安全* @param key 密钥* @param raw 需要加密的内容* @return*/public static String encrypt(byte[] key, String raw) throws Exception {// 第一次加密SecretKeySpec secretKey = new SecretKeySpec(key, "AES");byte[] enCodeFormat = secretKey.getEncoded();// 获取二次加密的keySecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES");Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM);// 向量iv,增加加密算法的强度IvParameterSpec iv = new IvParameterSpec(key);// 初始化加密器cipher.init(Cipher.ENCRYPT_MODE, secondSecretKey, iv);// 加密byte[] result = cipher.doFinal(raw.getBytes());// 进行base64编码return Base64Utils.encodeToString(result);}/*** 解密方法,使用key充当向量iv,增加加密算法的强度* @param key 密钥* @param enc 待解密内容* @return*/public static String decrypt(byte[] key, String enc) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(key, "AES");byte[] enCodeFormat = secretKey.getEncoded();// 二次加密SecretKeySpec secondSecretKey = new SecretKeySpec(enCodeFormat, "AES");Cipher cipher = Cipher.getInstance(Config.AES_ALGORITHM);IvParameterSpec iv = new IvParameterSpec(key);// 初始化cipher.init(Cipher.DECRYPT_MODE, secondSecretKey, iv);// 首先进行base64解码byte[] bytes = Base64Utils.decodeFromString(enc);// 解密byte[] result = cipher.doFinal(bytes);return new String(result);}public static void main(String[] args) throws Exception {//客户端代码String text = "hello";//随机生成16位aes密钥,也可以自己指定16位byte[] aesKey = getRandom(16).getBytes();String encryptText = AESCipher.encrypt(aesKey, text);System.out.println("加密后:\n" + encryptText);String decryptText = AESCipher.decrypt(aesKey, encryptText);System.out.println("解密后:\n" + decryptText);}
}
输出结果
加密后:
hwkYAF9eXj/dytmDBD30xg==
解密后:
hello
三、加解密 starter实战
1、介绍
加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdvice
和 RequestBodyAdvice
,利用这两个工具可以对请求和响应进行预处理,非常方便。
参考:
RSA+AES混合加密-JavaWebSpringBoot自定义starter
2、前期准备
2.1 引入依赖
因为我们这个工具是为 Web 项目开发的,以后必然使用在 Web 环境中,所以这里添加依赖时 scope 设置为 provided
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided</scope><version>2.7.0</version>
</dependency>
scope几个属性介绍
compile:默认值 他表示被依赖项目需要参与当前项目的编译,还有后续的测试,运行周期也参与其中,是一个比较强的依赖。打包的时候通常需要包含进去
test:依赖项目仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,例如:junit
runtime:表示被依赖项目无需参与项目的编译,不过后期的测试和运行周期需要其参与。与compile相比,跳过了编译而已。例如JDBC驱动,适用运行和测试阶段
provided:打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是打包阶段做了exclude操作
system:从参与度来说,和provided相同,不过被依赖项不会从maven仓库下载,而是从本地文件系统拿。需要添加systemPath的属性来定义路径
2.2 封装公共相应类
public class RespBean {private Integer status;private String msg;private Object obj;public static RespBean build() {return new RespBean();}public static RespBean ok(String msg) {return new RespBean(200, msg, null);}public static RespBean ok(String msg, Object obj) {return new RespBean(200, msg, obj);}public static RespBean error(String msg) {return new RespBean(500, msg, null);}public static RespBean error(String msg, Object obj) {return new RespBean(500, msg, obj);}private RespBean() {}private RespBean(Integer status, String msg, Object obj) {this.status = status;this.msg = msg;this.obj = obj;}public Integer getStatus() {return status;}public RespBean setStatus(Integer status) {this.status = status;return this;}public String getMsg() {return msg;}public RespBean setMsg(String msg) {this.msg = msg;return this;}public Object getObj() {return obj;}public RespBean setObj(Object obj) {this.obj = obj;return this;}
}
2.3 定义加解密工具类
加密这块有多种方案可以选择,对称加密、非对称加密,其中对称加密又可以使用 AES、DES、3DES 等不同算法,这里我们使用 Java 自带的 Cipher 来实现对称加密,使用 AES 算法
public class AESUtils {private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";// 获取 cipherprivate static Cipher getCipher(byte[] key, int model) throws Exception {SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");Cipher cipher = Cipher.getInstance(AES_ALGORITHM);cipher.init(model, secretKeySpec);return cipher;}// AES加密public static String encrypt(byte[] data, byte[] key) throws Exception {Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);return Base64.getEncoder().encodeToString(cipher.doFinal(data));}// AES解密public static byte[] decrypt(byte[] data, byte[] key) throws Exception {Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);return cipher.doFinal(Base64.getDecoder().decode(data));}
}
2.4 定义两个注解
接下来我们定义两个注解 @Decrypt
和 @Encrypt
。在以后使用的过程中,哪个接口方法添加了 @Encrypt 注解就对哪个接口的数据加密返回,哪个接口/参数添加了 @Decrypt 注解就对哪个接口/参数进行解密。另外就是 @Decrypt
可以用在参数上
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {}
2.5 设置自定义key
定义一个 EncryptProperties 类来读取用户配置的 key,这样就可以自定义key。这里设置了默认值,以后如果用户想自己配置 key,只需要在 application.properties 中配置 spring.encrypt.key=xxx
即可。
@ConfigurationProperties(prefix = "spring.encrypt")
@Component
public class EncryptProperties {// 这一块一定要16位或者整数倍,最多256private final static String DEFAULT_KEY = "www.shawn222.com";private String key = DEFAULT_KEY;public String getKey() {return key;}public void setKey(String key) {this.key = key;}
}
3、接口加密与解密
3.1 介绍
ResponseBodyAdvice
在你使用了 @ResponseBody
注解的时候才会生效,RequestBodyAdvice
在你使用了 @RequestBody
注解的时候才会生效,换言之,前后端都是 JSON 交互的时候,这两个才有用
3.2 接口加密
我们自定义 EncryptResponse
类实现 ResponseBodyAdvice
接口,泛型表示接口的返回类型,这里一共要实现两个方法
supports:这个方法用来判断什么样的接口需要加密,参数 returnType 表示返回类型,我们这里的判断逻辑就是方法是否含有
@Encrypt
注解,如果有,表示该接口需要加密处理,如果没有,表示该接口不需要加密处理。beforeBodyWrite:这个方法会在数据响应之前执行,也就是我们先对响应数据进行二次处理,处理完成后,才会转成 json 返回。我们这里的处理方式很简单,RespBean 中的 status 是状态码就不用加密了,另外两个字段重新加密后重新设置值即可。
另外需要注意,自定义的 ResponseBodyAdvice 需要用 @ControllerAdvice
注解来标记。
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<RespBean> {private ObjectMapper om = new ObjectMapper();@AutowiredEncryptProperties encryptProperties;@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return returnType.hasMethodAnnotation(Encrypt.class);}@Overridepublic RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {byte[] keyBytes = encryptProperties.getKey().getBytes();try {if (body.getMsg()!=null) {body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes));}if (body.getObj() != null) {body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes));}} catch (Exception e) {e.printStackTrace();}return body;}
}
3.3 接口解密
首先大家注意,DecryptRequest 类我们没有直接实现 RequestBodyAdvice
接口,而是继承自 RequestBodyAdviceAdapter 类,该类是 RequestBodyAdvice 接口的子类,并且实现了接口中的一些方法,这样当我们继承自 RequestBodyAdviceAdapter 时,就只需要根据自己实际需求实现某几个方法即可。
supports:该方法用来判断哪些接口需要处理接口解密,我们这里的判断逻辑是方法上或者参数上含有
@Decrypt
注解的接口,处理解密问题。beforeBodyRead:这个方法会在参数转换成具体的对象之前执行,我们先从流中加载到数据,然后对数据进行解密,解密完成后再重新构造 HttpInputMessage 对象返回。
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {@AutowiredEncryptProperties encryptProperties;@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);}@Overridepublic HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {byte[] body = new byte[inputMessage.getBody().available()];inputMessage.getBody().read(body);try {byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);return new HttpInputMessage() {@Overridepublic InputStream getBody() throws IOException {return bais;}@Overridepublic HttpHeaders getHeaders() {return inputMessage.getHeaders();}};} catch (Exception e) {e.printStackTrace();}return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);}
}
4、打包发布starter
4.1 定义自动化配置类
// 换成自己的包路径
@Configuration
@ComponentScan("com.example.encryption")
public class EncryptAutoConfiguration {}
最后,resources
目录下定义 META
-INF,然后再定义 spring.factories
文件,这样当项目启动时,就会自动加载该配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.encryption.EncryptAutoConfiguration
安装到本地仓库比较简单,直接 mvn install
,或者在 IDEA 中,点击右边的 Maven,然后双击 install
4.2 发布线上使用
发不到线上我们可以使用 JitPack来做。首先我们在 GitHub 上创建一个仓库,将我们的代码上传上去,上传成功后,点击右边的 Create a new release
按钮,发布一个正式版
发布成功后,打开 jitpack,输入仓库的完整路径,点击 lookup 按钮,查找到之后,再点击 Get it
按钮完成构建,构建成功后,JitPack 上会给出项目引用方式,新建项目时引入即可
5、新项目使用
创建实体类
public class User {private Long id;private String username;//省略 getter/setter
}
创建测试类,第一个接口使用了 @Encrypt
注解,所以会对该接口的数据进行加密(如果不使用该注解就不加密),第二个接口使用了 @Decrypt
所以会对上传的参数进行解密,注意 @Decrypt
注解既可以放在方法上也可以放在参数上。
@RestController
public class HelloController {@GetMapping("/user")@Encryptpublic RespBean getUser() {User user = new User();user.setId((long) 99);user.setUsername("javaboy");return RespBean.ok("ok", user);}@PostMapping("/user")public RespBean addUser(@RequestBody @Decrypt User user) {System.out.println("user = " + user);return RespBean.ok("ok", user);}
}
参考文章
如何优雅的实现 SpringBoot 接口参数加密解密?
为什么使用 Java Cipher 要指定转换模式?
Hutool加密解密
【网络】java密码安全
SpringBoot接口加密与解密相关推荐
- SpringBoot 接口加密解密,新姿势!
我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华.如果小伙伴们觉得不错就一键三连吧~ 声明:原文作者:微信公众号:方志朋 文章目录 1 . 介绍 2 . 前置知识 2.1 hutool-cry ...
- SpringBoot 接口加密解密,新姿势
1. 介绍 在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用 如果我们想保证数据传输的安全,对接口出参加密,入参解密. 但是不想写重复代码,我们可以提供一个通用star ...
- SpringBoot 接口数据加解密技巧
对接口整体进行加密处理 需求解析: 服务端.客户端和H5统一拦截加解密,网上有成熟方案,也可以按其他服务中实现的加解密流程来搞: 使用AES放松加密,考虑到H5端存储密钥安全性相对来说会低一些,故分针 ...
- 【转载】SpringBoot 接口数据加解密技巧,so easy!
在不影响原代码逻辑下,梳理了相关技术方案,主要的需求点如下: 尽量少改动,不影响之前的业务逻辑: 考虑到时间紧迫性,可采用对称性加密方式,服务需要对接安卓.IOS.H5三端,另外考虑到H5端存储密钥安 ...
- SpringBoot 接口数据加解密
xx项目有于安全问题,需要对接口整体进行加密处理,我们怎么处理呢. 和产品.前端讨论需求后,梳理了相关技术方案,主要的需求点如下: 尽量少改动,不影响之前的业务逻辑: 考虑到时间紧迫性,可采用对称性加 ...
- SpringBoot 接口数据加解密技巧,so easy!
点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...
- SpringBoot 接口数据加解密实战!
这日,刚撸完2行代码,正准备掏出手机摸鱼放松放松,只见老大朝我走过来,并露出一个"善意"的微笑,兴伟呀,xx项目有于安全问题,需要对接口整体进行加密处理,你这方面比较有经验,就给你 ...
- SpringBoot 接口数据加解密实战
这日,刚撸完2行代码,正准备掏出手机摸鱼放松放松,只见老大朝我走过来,并露出一个"善意"的微笑,兴伟呀,xx项目有于安全问题,需要对接口整体进行加密处理,你这方面比较有经验,就给你 ...
- SpringBoot接口数据加解密实战
主要的需求点如下: 尽量少改动,不影响之前的业务逻辑: 考虑到时间紧迫性,可采用对称性加密方式,服务需要对接安卓.IOS.H5三端,另外考虑到H5端存储密钥安全性相对来说会低一些,故分针对H5和安卓. ...
- java .net 加密解密,【汉字加密解密】一个16进制,可用于.net与java接口加密、解密...
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 /** * 得到16进制的GBK编码,转换为汉字 * @param hexStr * @return */ public static String de ...
最新文章
- Linux使用dd命令快速生成大文件
- 【快乐水题】1688. 比赛中的配对次数
- hash hashcode变化_没想到 Hash 冲突还能这么玩,你的服务中招了吗?
- 对struts一点理解总结
- 数据库管家----ADODB类库.
- Intel 64/x86_64/IA-32/x86处理器 - 锁原子操作(1) - 处理器保证的原子操作
- CSS Margin(外边距)
- Java--UI--弹出对话框
- 基于遗传算法的车辆优化调度-matlab代码 考虑供应过剩惩罚、供应不足惩罚成本
- iframe是什么?iframe用法详解。
- typedef struct Lnode{ Elemtype data; struct Lnode *next; } Lnode,*Linklist;
- 主机电源供电端口-24PIN
- python无限循环怎么结束,python的无限循环及退出
- 黑客组织“洋葱狗”潜伏3年终曝光 定期偷袭能源及交通行业
- pod2g宣布A5的Sandbox破解成功
- Gentoo Linux+KDE Plasma桌面安装教程
- 基于Scrapy框架爬取豆瓣《复联4》影评,并生成词云
- pycharm安装mysql驱动包
- bernoulli vs binominal vs multinoulli vs multinomial
- 初学前端,学习路线图必不可少,更有【95页】初级前端模块笔记!