Java 用AOP切面实现对实体么个属性加密解密
一、准备加密工具
我这里准备的是SM2加密方式,这里加密工具,根据自己的需要准备相应的加密工具即可,我的代码会在上传到git,可自行下载。
1.获取公钥私钥
在SM2Utils类里面有随机获取的方式
/*** @method generateKeyPair* @desc 生成随机秘钥对* @version V1.0.0* @Param:* @author xukang* @date 2022/1/25 15:26* @return void*/public static void generateKeyPair() {SM2 sm2 = SM2.Instance();AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();BigInteger privateKey = ecpriv.getD();ECPoint publicKey = ecpub.getQ();System.out.println("公钥: " + Util.byteToHex(publicKey.getEncoded()));System.out.println("私钥: " + Util.byteToHex(privateKey.toByteArray()));}
main方法运行可获得
public static void main(String[] args) throws Exception {//生成密钥对 generateKeyPair();}

2.验证加密工具是否正确
在SM2Utils工具类里面分别有加密方式和解密方式,拿到刚刚生成的公钥和私钥,编写测试main方法,验证加密解密是否正确。
public static void main(String[] args) throws Exception {//生成密钥对 generateKeyPair();String plainText = "abcA沙发上的范德萨的发生富商大贾的三的撒范德萨发送到是打发富士达爱是飞洒发送到大丰收大丰收的abc";byte[] sourceData = plainText.getBytes();//下面的秘钥可以使用generateKeyPair()生成的秘钥内容// 国密规范正式私钥String prik = "1565711FE3C1F48046B5B01060D12493F3F7631ABAF5CC3F09BB2890DF5317E7";// 国密规范正式公钥String pubk = "048BB63E6FD9622759AD7173F9EB670200500D02B1F11026FB27CF6F3007D60DE84060AF22C23DF8C9C8ED0C5EE76FBEFE4631C8A0C66ED0C3368867B1745339D6";System.out.println("加密: ");String cipherText = SM2Utils.encrypt(Util.hexToByte(pubk), sourceData);System.out.println(cipherText);System.out.println("解密: ");plainText = new String(SM2Utils.decrypt(Util.hexToByte(prik), Util.hexToByte(cipherText)));System.out.println(plainText);}
得到的结果如下:

自此,加密解密工具正确
二、编写加密解密切面
1.编写属性注解
加密属性注解:
import org.springframework.core.annotation.Order;import java.lang.annotation.*;/***@类名 EncryptField*@描述 加密属性注解*@版本 1.0*@创建人 XuKang*@创建时间 2022/1/25 16:14**/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
解密属性注解:
import org.springframework.core.annotation.Order;import java.lang.annotation.*;/*** @类名 DecryptField* @描述 解密属性注解* @版本 1.0* @创建人 XuKang* @创建时间 2022/1/25 15:31**/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
}
2.编写加密解密方法注解
加密方法注解:
import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod {
}
解密方法注解:
import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptMethod {
}
3.编写切面
package com.security.aspect;import com.google.gson.Gson;
import com.security.annotation.DecryptField;
import com.security.annotation.EncryptField;
import com.security.smutils.SM2Utils;
import com.security.smutils.Util;
import com.security.utils.Md5Utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.Objects;/***@类名 Sm4Aspect*@描述 加密解密切面*@版本 1.0*@创建人 XuKang*@创建时间 2022/1/25 15:29**/
@Aspect
@Component
public class Sm2Aspect {private Logger log = LoggerFactory.getLogger(Sm2Aspect.class);//下面的秘钥可以使用generateKeyPair()生成的秘钥内容// 国密规范正式私钥public static String prik = "00931DAA2D92B1788AF7C0340F22E71B6631FBCA4BC86790FF11090EB9316442F5";// 国密规范正式公钥public static String pubk = "04D0C84966BDE748E4E244F16B1F4F507B665E236F854624593B17931524BD8BD1E8B28C4409E56EEDA7C38F71E4C16C14886FDEDACFC555579131E080E14D0D38";public Sm2Aspect() {}@Pointcut("@annotation(com.security.annotation.EncryptMethod)")public void EncryptAOPCut() {}@Pointcut("@annotation(com.security.annotation.DecryptMethod)")public void DecryptAOPCut() {}/*** @method encryptMethodAop* @desc 加密方法* @version V1.0.0* @Param: joinPoint* @author xukang* @date 2022/1/25 15:29* @return Object*/@Around("EncryptAOPCut()")public Object encryptMethodAop(ProceedingJoinPoint joinPoint) {Object responseObj = null;try {responseObj = joinPoint.proceed();this.handleEncrypt(responseObj);//md5加密,在相应里面设置签名值String md5Data = Md5Utils.getMD5String(new Gson().toJson(responseObj));HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();//获取responseresponse.setHeader("md5",md5Data);} catch (Throwable throwable) {throwable.printStackTrace();this.log.error("encryptMethodAop处理出现异常{}", throwable);}return responseObj;}/*** @method decryptMethodAop* @desc 解密方法* @version V1.0.0* @Param: joinPoint* @author xukang* @date 2022/1/25 15:30* @return Object*/@Around("DecryptAOPCut()")public Object decryptMethodAop(ProceedingJoinPoint joinPoint) {Object responseObj = null;try {responseObj = joinPoint.getArgs()[0];//throw new RuntimeException("md5校验失败");this.handleDecrypt(responseObj);//生成md5签名String md5 = "";md5 = Md5Utils.getMD5String(new Gson().toJson(responseObj));System.out.println(md5);//从请求头获取前端传过来的签名String origianlMd5 = "";HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();origianlMd5 = request.getHeader("md5");//比对签名//if (md5.equals(origianlMd5))if(md5.equals(origianlMd5)){//方便调试,不比对前端签名responseObj = joinPoint.proceed();}else{this.log.error("参数的md5校验不同,可能存在篡改行为,请检查!");throw new Exception("参数的md5校验不同,可能存在篡改行为,请检查!");}} catch (Throwable throwable) {throwable.printStackTrace();this.log.error("decryptMethodAop处理出现异常{}", throwable);}return responseObj;}/*** @method handleEncrypt* @desc 对实体么个属性进行加密* @version V1.0.0* @Param: requestObj* @author xukang* @date 2022/1/25 15:30* @return void*/private void handleEncrypt(Object requestObj) throws Exception {if (!Objects.isNull(requestObj)) {Field[] fields = requestObj.getClass().getDeclaredFields();Field[] fieldsCopy = fields;int fieldLength = fields.length;for(int i = 0; i < fieldLength; ++i) {Field field = fieldsCopy[i];boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);if (hasSecureField) {field.setAccessible(true);String plaintextValue = (String)field.get(requestObj);String cipherText = SM2Utils.encrypt(Util.hexToByte(pubk), plaintextValue.getBytes());field.set(requestObj, cipherText);}}}}/*** @method handleDecrypt* @desc 对实体么个属性进行解密* @version V1.0.0* @Param: responseObj* @author xukang* @date 2022/1/25 15:31* @return*/private Object handleDecrypt(Object responseObj) throws Exception {if (Objects.isNull(responseObj)) {return null;} else {Field[] fields = responseObj.getClass().getDeclaredFields();Field[] fieldsCopy = fields;int fieldLength = fields.length;for(int i = 0; i < fieldLength; ++i) {Field field = fieldsCopy[i];boolean hasSecureField = field.isAnnotationPresent(DecryptField.class);if (hasSecureField) {field.setAccessible(true);String encryptValue = (String)field.get(responseObj);encryptValue = new String(SM2Utils.decrypt(Util.hexToByte(prik), Util.hexToByte(encryptValue)));field.set(responseObj, encryptValue);}}return responseObj;}}
}
4.切面要主要的地方
1.加密解密方式可以自行更换;
2.加密解密有数据签名,如果是前端请求需要对数据按照一定顺序进行md5签名,后端会根据解密后的参数进行md5验签,如果前端和后端生成的md5签名字符串不一致,这判断为请求数据被篡改,异常处理;
后端生成签名 -> 从请求头拿到签名 -> 比对签名

3.后端返回数据,前端也需要验签;

5.增加实体
import com.security.annotation.DecryptField;
import com.security.annotation.EncryptField;
import lombok.Data;@Data
public class Info {@DecryptField@EncryptFieldprivate String name;private String sex;}
@DecryptField @EncryptField 分别是解密加密标记注解,在切面中判断标记:


三、通过接口测试
1.不传签名或传错误的

请求结果为异常:

日志打印为:

2.传正确的签名进行请求

请求结果:

四、项目地址
项目地址:https://gitcode.net/m0_38075227/spring-boot-security
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
