切面AOP注解来实现AES + RSA 加解密

前言

大概2个多月前,帮一个同事一起解决使用AES和RSA非对称加密算法来对数据进行解密,需要写一个切面,然后在controller层的接口上直接添加注解,就可以对加密的数据进行解密。

由于自己之前从未涉及到这块,也一直觉得切面是难啃的硬骨头,而这次经历让我对切面有了更深的理解,忽然直接切面在我面前也如猪蹄一样香,被我分崩离析了。
所以这篇博客,涉及到2个技术点:

  1. AOP的around通知
  2. AES+RSA非对称加密

当然里面的切面是从大神那里复制过来,然后自己也写了demo,但是复制过来之后并不能直接用,自己也通过不断地修改,最终实现了对加密数据地解密,解密结果再加密,以及对原数据进行加密。

我把项目详细的内容都粘贴出来了,本来是想只粘贴切面和加解密的工具类,剩下的传到github上,但是中途出了差错,切分支的时候不小心把东西全部整没了,还好还有class文件,否则我得花更多的时间恢复,这也就是为啥我类文件上的注释是2020-11-09的时间。

复制起来确实麻烦,所以我还是把github的地址粘贴出来:

https://github.com/fanhuifangGit/annotation-test.git
看着我这么辛苦的份上希望留下你的足迹和star!!!【此处应该有表情】
(这个小的demo也差不多断断续续的写了一个星期了,也算是小有收获吧!!!)

目录结构

目录结构

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fanhf</groupId>
    <artifactId>annotation-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>annotation-test</name>
    <description>Demo project for Spring Boot</description>


    <dependencies>
        <!-- spring-boot-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- springboot web(MVC)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>


        <!-- springboot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- thymeleaf模板 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>

        <!-- Base64编码需要  -->
        <dependency>
            <groupId>org.apache.directory.studio</groupId>
            <artifactId>org.apache.commons.codec</artifactId>
            <version>1.8</version>
        </dependency>

        <!--提供更多的加密、填充方式-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>

        <!--aop 面向切面-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

    </dependencies>

    <!--构建工具-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <finalName>${project.artifactId}</finalName>
                    <outputDirectory>../package</outputDirectory>
                </configuration>
            </plugin>
            <!-- 跳过启动测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置文件:application.properties

#内置Tomcat容器配置
server.port = 8882

#基础配置
#选择配置分支,先读取系统环境变量,如果没有则默认值为 dev
spring.profiles.active = ${BASE_ADMIN:dev} 
spring.application.name = annotation-test

启动类:AnnotationTestApplication

package com.fanhf.annotationtest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AnnotationTestApplication {
    private static final Logger log = LoggerFactory.getLogger(AnnotationTestApplication.class);

    public AnnotationTestApplication() {}

    public static void main(String[] args) {
        SpringApplication.run(AnnotationTestApplication.class, args);
        log.info("启动成功!!!!!!");
    }
}

解密注解:Decrypt

package com.fanhf.annotationtest.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

加密注解:Encrypt

package com.fanhf.annotationtest.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

bean:

EncryptBean

package com.fanhf.annotationtest.bean;

/**
 * @author fanhf
 * @Description 加密后的bean字段
 * @date 2020-11-09 14:06
 */
public class EncryptBean {
    private String aesData;
    private String rsaData;

    public String getAesData() {
        return aesData;
    }

    public void setAesData(String aesData) {
        this.aesData = aesData;
    }

    public String getRsaData() {
        return rsaData;
    }

    public void setRsaData(String rsaData) {
        this.rsaData = rsaData;
    }
}

UserBean

package com.fanhf.annotationtest.bean;

/**
 * @author fanhf
 * @Description 要加密的bean类
 * @date 2020-11-09 12:43
 */
public class UserBean {
    private String passwd;
    private String name;
    private Integer age;

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Result

package com.fanhf.annotationtest.bean;

import lombok.Data;

import java.io.Serializable;

/**
 * @author fanhf
 * @Description 结果返回的统一对象
 * @date 2020-11-09 12:45
 */

@Data
public class Result<T> implements Serializable {
    /**
     * 通信数据
     */
    private T data;

    /**
     * 通过静态方法获取实例
     */
    public static <T> Result<T> of(T data) {
        return new Result<>(data);
    }


    @Deprecated
    public Result() {
    }

    private Result(T data) {
        this.data = data;
    }
}

controller控制层

package com.fanhf.annotationtest.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fanhf.anotation.Decrypt;
import com.fanhf.anotation.Encrypt;
import com.fanhf.result.Result;
import com.fanhf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    /**
     * 我写了4个接口,前2个接口是对应切面SafetyAspect
     * 后2个接口是对应SafetyAspectPlus
     */
    /**
     * 1、对密码加密
     */
    @PostMapping(value = "/encrypt")
    @Encrypt
    public JSONObject encrypt(EncryptBean encryptBean) {
        return (JSONObject) JSONObject.toJSON(encryptBean);
    }

    /**POST
     * 2、对密码解密
     * 解密之后是用userBean来接
     */
    @PostMapping(value = "/decrypt")
    @Decrypt
    public JSONObject decrypt(UserBean userBean) {
        return (JSONObject) JSONObject.toJSON(userBean);
    }

    /**POST
     * 3、对密码解密后处理后再加密
     * 解密之后是用userBean来接
     */
    @PostMapping(value = "/ende")
    @Decrypt
    @Encrypt
    public Result<UserBean> ende(UserBean userBean) {
        UserBean userBeanNew = userService.updatePasswd(userBean);
        return Result.of(userBeanNew);
    }

    /**
     *  4、加解密
     * @date 2020/11/6 17:58
     * @return JSONObject
     * 说明:这个接口是无法正常放回加密后的数据,原因是参数和返回值不一致
     **/

    @PostMapping(value = "/endeplus")
    @Decrypt
    @Encrypt
    public JSONObject endeplus(UserBean userBean) {
        UserBean userBeanNew = userService.updatePasswd(userBean);
        return (JSONObject) JSONObject.toJSON(userBeanNew);
    }
}

UserService业务层

package com.fanhf.annotationtest.service;

import com.fanhf.controller.UserBean;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public UserBean updatePasswd(UserBean userBean) {
        userBean.setPasswd("@"+userBean.getPasswd()+"@");
        userBean.setAge(userBean.getAge()+1);
        userBean.setName(userBean.getName()+"AOP");
        return  userBean;
    }
}

说明:我的这个service不涉及到数据库,但在真实地业务场景下应该是需要将解密后的数据进行处理,处理完之后再对数据库进行修改,之后再将处理的数据二次加密再返回给前端。我这里就简化了。

再来看看AES的加密工具类:AesUtil

package com.fanhf.annotationtest.utils;

import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Random;

/**
 * AES加、解密算法工具类
 */
public class AesUtil {
    /**
     * 加密算法AES
     */
    private static final String KEY_ALGORITHM = "AES";

    /**
     * key的长度,Wrong key size: must be equal to 128, 192 or 256
     * 传入时需要16、24、36
     */
    private static final Integer KEY_LENGTH = 16 * 8;

    /**
     * 算法名称/加密模式/数据填充方式
     * 默认:AES/ECB/PKCS5Padding
     */
    private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";

    /**
     * 后端AES的key,由静态代码块赋值
     */
    public static String key;

    /**
     * 不能在代码中创建
     * JceSecurity.getVerificationResult 会将其put进 private static final Map<Provider,Object>中,导致内存缓便被耗尽
     */
    private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();

    static {
        key = getKey();
    }

    /**
     * 获取key
     */
    public static String getKey() {
        StringBuilder uid = new StringBuilder();
        //产生16位的强随机数
        Random rd = new SecureRandom();
        for (int i = 0; i < KEY_LENGTH / 8; i++) {
            //产生0-2的3位随机数
            int type = rd.nextInt(3);
            switch (type) {
                case 0:
                    //0-9的随机数
                    uid.append(rd.nextInt(10));
                    break;
                case 1:
                    //ASCII在65-90之间为大写,获取大写随机
                    uid.append((char) (rd.nextInt(25) + 65));
                    break;
                case 2:
                    //ASCII在97-122之间为小写,获取小写随机
                    uid.append((char) (rd.nextInt(25) + 97));
                    break;
                default:
                    break;
            }
        }
        return uid.toString();
    }

    /**
     * 加密
     *
     * @param content    加密的字符串
     * @param encryptKey key值
     */
    public static String encrypt(String content, String encryptKey) throws Exception {
        //设置Cipher对象
        Cipher cipher = Cipher.getInstance(ALGORITHMS, PROVIDER);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));

        //调用doFinal
        // 转base64
        return Base64.encodeBase64String(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 解密
     *
     * @param encryptStr 解密的字符串
     * @param decryptKey 解密的key值
     */
    public static String decrypt(String encryptStr, String decryptKey) throws Exception {
        //base64格式的key字符串转byte
        byte[] decodeBase64 = Base64.decodeBase64(encryptStr);

        //设置Cipher对象
        Cipher cipher = Cipher.getInstance(ALGORITHMS,PROVIDER);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));

        //调用doFinal解密
        return new String(cipher.doFinal(decodeBase64));
    }
    public static void  main(String[] args) throws Exception {
        String key = getKey();
        System.out.println("key:"+key);
        String en = encrypt("asdf",key);
        System.out.println("加密后的结果:"+en);
        System.out.println(decrypt(en,key));
    }
}

再来看看RSA的加密工具类:RsaUtil

package com.fanhf.annotationtest.utils;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;

import javax.crypto.Cipher;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * RSA加、解密算法工具类
 */
@Slf4j
public class RsaUtil {

    /**
     * 加密算法AES
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 算法名称/加密模式/数据填充方式
     * 默认:RSA/ECB/PKCS1Padding
     */
    private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";

    /**
     * Map获取公钥的key
     */
    private static final String PUBLIC_KEY = "publicKey";

    /**
     * Map获取私钥的key
     */
    private static final String PRIVATE_KEY = "privateKey";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
     */
    private static final int INITIALIZE_LENGTH = 1024;

    /**
     * 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值
     */
    private static Map<String, Object> genKeyPair = new LinkedHashMap<>();

    static {
        try {
            genKeyPair.putAll(genKeyPair());
        } catch (Exception e) {
            //输出到日志文件中
            e.printStackTrace();
        }
    }

    /**
     * 生成密钥对(公钥和私钥)
     */
    private static Map<String, Object> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(INITIALIZE_LENGTH);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<>(2);
        //公钥
        keyMap.put(PUBLIC_KEY, publicKey);
        //私钥
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 私钥解密
     *
     * @param encryptedData 已加密数据
     * @param privateKey    私钥(BASE64编码)
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));

        //设置加密、填充方式
        /*
            如需使用更多加密、填充方式,引入
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-jdk16</artifactId>
                <version>1.46</version>
            </dependency>
            并改成
            Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
         */
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        //分段进行解密操作
        return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_DECRYPT_BLOCK);
    }

    /**
     * 公钥加密
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));

        //设置加密、填充方式
        /*
            如需使用更多加密、填充方式,引入
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-jdk16</artifactId>
                <version>1.46</version>
            </dependency>
            并改成
            Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
         */
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        //分段进行加密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
    }

    /**
     * 获取私钥
     */
    public static String getPrivateKey() {
        Key key = (Key) genKeyPair.get(PRIVATE_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * 获取公钥
     */
    public static String getPublicKey() {
        Key key = (Key) genKeyPair.get(PUBLIC_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * 分段进行加密、解密操作
     */
    private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > encryptBlock) {
                cache = cipher.doFinal(data, offSet, encryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * encryptBlock;
        }
        out.close();
        return out.toByteArray();
    }
}

AES和RSA的加密算法不是我写的,而是从同事那里copy的,网上也有一些关于AES和RAS算法的博客,大家可自行学习,我就不赘述了,这里只展示具体的实现。

切面1:SafetyAspect

package com.fanhf.annotationtest.aspectj;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fanhf.annotationtest.annotation.Decrypt;
import com.fanhf.annotationtest.annotation.Encrypt;
import com.fanhf.annotationtest.bean.EncryptBean;
import com.fanhf.annotationtest.utils.AesUtil;
import com.fanhf.annotationtest.utils.RsaUtil;
import org.apache.commons.codec.binary.Base64;
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.aspectj.lang.reflect.MethodSignature;
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 java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @author fanhf
 * @Description AES + RSA 加解密AOP处理
 * @date 2020-11-09 13:47
 */
@Aspect
@Component
public class SafetyAspect {

    private static final Logger log = LoggerFactory.getLogger(SafetyAspectPlus.class);
    /**
     *这里的私钥和公钥可以替换,只要执行RsaUtils的main方法就可以获取到。
     * RSA加解密的时候需要根据公钥和私钥来进行加解密,真实的业务场景一般是从前端传过来公钥,私钥是随机获取的,我这里为了方便,就写死了。
     **/
    private static final String PRIVATEKEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALwT6AsfbEV/d5Mv+fbsaxjAKDPFmJu2JnV3b03T6RX1Oailg1tawbI3boR2PC1lOjRBHoqR8GPJGNu79NmnaZ9Fq5gqUZkO/K88/BdRgiqropEqLginYiiDJm22jqdFUb2VafwLj2ou5sevtbIR6fVou881k/YRsAImsA3WH7ITAgMBAAECgYAndmnOw6YdIvS8/mkNZWfHRrJowoIV0e9Z4FiLVPZoNA8IEspwBaf0s+rNgl14DPBcfHljC+ILnetIV7S1Yoonk5epsq7BjmrB7AqXazdXLv2Cmyw8CyGjs7ShhBZPn/oAmCWIQgObptDBGrdB9bZk1s7mSr96Z4fIw9/mLXuqsQJBAPI3mnjfVcr5A5KysZezqjljYGgf5cboP/t956H+LqMLkh2DC8nbiSoOy5etWOvoarRb6t8bWc4/crCXQvvTrIkCQQDGx6ZMoLURLz8KLCRKjpUt5Dx0jmu3utjPlmfLxpiAI0VvokWpFZrc4mlib/T4zDtIDFGzGBkSC7UhJ4339Nq7AkAGv6ncKEzZpOqGkdgE5AqgIray8ACU9C+kMDPd/ZkLDe16SQZxD17Y/ySJC1lo6Ubf05fNs5Ni/b2SUgSZw6IRAkAVbDjg80TwWC4sE3vJyToMmxdk3GCBiZKKNMR08q9GyAZYtJ1bTqfE/GWtJTG6ipAtAJ7hdUxmZHqd2xxyx6G3AkASaSgCdDxd7N6O6qZs0T5V2z9PzXj1exsUwIPUtVC1EnZ+OmklUBVqRHtqw+xSwomfcKvb7zx8T9L3oCgx6okj";
    private static final String PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8E+gLH2xFf3eTL/n27GsYwCgzxZibtiZ1d29N0+kV9TmopYNbWsGyN26EdjwtZTo0QR6KkfBjyRjbu/TZp2mfRauYKlGZDvyvPPwXUYIqq6KRKi4Ip2IogyZtto6nRVG9lWn8C49qLubHr7WyEen1aLvPNZP2EbACJrAN1h+yEwIDAQAB";
    /**
     * Pointcut 切入点
     */
    @Pointcut("execution(public * com.fanhf.annotationtest.controller.UserController.encrypt(..))||" +
              "execution(public * com.fanhf.annotationtest.controller.UserController.decrypt(..))")

    public void safetyAspect() {}

    /**
     * 环绕通知
     */
    @Around(value = "safetyAspect()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            assert attributes != null;

            //request对象
            HttpServletRequest request = attributes.getRequest();

            //http请求方法  post get
            String httpMethod = request.getMethod().toLowerCase();

            //method方法
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();

            //method方法上面的注解
            Annotation[] annotations = method.getAnnotations();

            //方法的形参参数
            Object[] args = pjp.getArgs();

            //是否有@Decrypt
            boolean hasDecrypt = false;
            //是否有@Encrypt
            boolean hasEncrypt = false;
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Decrypt.class) {
                    hasDecrypt = true;
                }
                if (annotation.annotationType() == Encrypt.class) {
                    hasEncrypt = true;
                }
            }

            InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "UTF-8");
            StringBuilder stringBuilder;
            try (BufferedReader br = new BufferedReader(inputStreamReader)) {
                String line;
                stringBuilder = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    stringBuilder.append(line);
                }
            }
            String oridata = stringBuilder.toString();
            JSONObject jsonObject = JSONObject.parseObject(oridata);
            String line = prettyJson(oridata);
            log.info("入参的切面json串:\r\n{}", line);

            if ("post".equals(httpMethod) && hasDecrypt) {
                String data = null;
                if (oridata.indexOf("data") != -1) {
                    data = jsonObject.getString("data");
                    jsonObject = JSONObject.parseObject(data);
                }
                //AES加密后的数据
                String aesKey = jsonObject.getString("aesData");
                //后端RSA公钥加密后的AES数据
                String dataString = jsonObject.getString("rsaData");
                //后端私钥解密的到AES的key

                byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(dataString), PRIVATEKEY);

                String rsaData = new String(plaintext);
                log.info("解密出来的AES生成的key为:{}", rsaData);

                String originData = AesUtil.decrypt(aesKey, rsaData);
                log.info("解密出来的data数据:\r\n{}", prettyJson(originData));
                if (args.length > 0) {
                    args[0] = JSONObject.parseObject(originData, args[0].getClass());
                }
            }

            if (hasEncrypt) {
                String  aesKey = AesUtil.getKey();
                log.info("AES的key:{}", aesKey);

                String  dataString = jsonObject.toJSONString();
                String aesData = AesUtil.encrypt(dataString, aesKey);
                log.info("AesUtil加密后的数据:{}", aesData);

                String rsaData = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(aesKey.getBytes(), PUBLICKEY));
                log.info("RsaUtil加密后的data数据:{}", rsaData);

                EncryptBean encryptBean = new EncryptBean();
                encryptBean.setAesData(aesData);
                encryptBean.setRsaData(rsaData);

                log.info("加密后的数据为:{}", JSONObject.toJSONString(encryptBean));
                if (args.length > 0) {
                    args[0] = JSONObject.parseObject(JSONObject.toJSONString(encryptBean), args[0].getClass());
                }
            }

            return pjp.proceed(args);

        } catch (Throwable e) {
            log.error(e.getMessage());
            return JSONObject.parseObject("{ \"加解密异常\": \"" +e.getMessage() + "\"}");
        }
    }

    public static String prettyJson(String reqJson) {
        JSONObject object = JSONObject.parseObject(reqJson);
        return JSON.toJSONString(object, new SerializerFeature[]{SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat});
    }
}   

切面2:SafetyAspectPlus

package com.fanhf.annotationtest.aspectj;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fanhf.annotationtest.annotation.Decrypt;
import com.fanhf.annotationtest.annotation.Encrypt;
import com.fanhf.annotationtest.bean.Result;
import com.fanhf.annotationtest.utils.AesUtil;
import com.fanhf.annotationtest.utils.RsaUtil;
import org.apache.commons.codec.binary.Base64;
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.aspectj.lang.reflect.MethodSignature;
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 java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @author fanhf
 * @Description AES + RSA 加解密AOP处理
 * @date 2020-11-09 13:47
 */

@Aspect
@Component
public class SafetyAspectPlus {

    private static final Logger log = LoggerFactory.getLogger(SafetyAspectPlus.class);
    /**
     *这里的私钥和公钥可以替换,只要执行RsaUtils的main方法就可以获取到。
     * RSA加解密的时候需要根据公钥和私钥来进行加解密,真实的业务场景一般是从前端传过来公钥,私钥是随机获取的,我这里为了方便,就写死了。
     **/
    private static final String PRIVATEKEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALwT6AsfbEV/d5Mv+fbsaxjAKDPFmJu2JnV3b03T6RX1Oailg1tawbI3boR2PC1lOjRBHoqR8GPJGNu79NmnaZ9Fq5gqUZkO/K88/BdRgiqropEqLginYiiDJm22jqdFUb2VafwLj2ou5sevtbIR6fVou881k/YRsAImsA3WH7ITAgMBAAECgYAndmnOw6YdIvS8/mkNZWfHRrJowoIV0e9Z4FiLVPZoNA8IEspwBaf0s+rNgl14DPBcfHljC+ILnetIV7S1Yoonk5epsq7BjmrB7AqXazdXLv2Cmyw8CyGjs7ShhBZPn/oAmCWIQgObptDBGrdB9bZk1s7mSr96Z4fIw9/mLXuqsQJBAPI3mnjfVcr5A5KysZezqjljYGgf5cboP/t956H+LqMLkh2DC8nbiSoOy5etWOvoarRb6t8bWc4/crCXQvvTrIkCQQDGx6ZMoLURLz8KLCRKjpUt5Dx0jmu3utjPlmfLxpiAI0VvokWpFZrc4mlib/T4zDtIDFGzGBkSC7UhJ4339Nq7AkAGv6ncKEzZpOqGkdgE5AqgIray8ACU9C+kMDPd/ZkLDe16SQZxD17Y/ySJC1lo6Ubf05fNs5Ni/b2SUgSZw6IRAkAVbDjg80TwWC4sE3vJyToMmxdk3GCBiZKKNMR08q9GyAZYtJ1bTqfE/GWtJTG6ipAtAJ7hdUxmZHqd2xxyx6G3AkASaSgCdDxd7N6O6qZs0T5V2z9PzXj1exsUwIPUtVC1EnZ+OmklUBVqRHtqw+xSwomfcKvb7zx8T9L3oCgx6okj";
    private static final String PUBLICKEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8E+gLH2xFf3eTL/n27GsYwCgzxZibtiZ1d29N0+kV9TmopYNbWsGyN26EdjwtZTo0QR6KkfBjyRjbu/TZp2mfRauYKlGZDvyvPPwXUYIqq6KRKi4Ip2IogyZtto6nRVG9lWn8C49qLubHr7WyEen1aLvPNZP2EbACJrAN1h+yEwIDAQAB";
    /**
     * Pointcut 切入点
     */
    @Pointcut("execution(public * com.fanhf.annotationtest.controller.UserController.ende(..))")
    public void safetyAspectPlus() {}
    /**
     * 环绕通知
     */
    @Around(value = "safetyAspectPlus()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            assert attributes != null;

            //request对象
            HttpServletRequest request = attributes.getRequest();

            //http请求方法  post get
            String httpMethod = request.getMethod().toLowerCase();

            //method方法
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();

            //method方法上面的注解
            Annotation[] annotations = method.getAnnotations();

            //方法的形参参数
            Object[] args = pjp.getArgs();

            //是否有@Decrypt
            boolean hasDecrypt = false;
            //是否有@Encrypt
            boolean hasEncrypt = false;
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Decrypt.class) {
                    hasDecrypt = true;
                }
                if (annotation.annotationType() == Encrypt.class) {
                    hasEncrypt = true;
                }
            }

            InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), "UTF-8");
            StringBuilder stringBuilder;
            try (BufferedReader br = new BufferedReader(inputStreamReader)) {
                String line;
                stringBuilder = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    stringBuilder.append(line);
                }
            }
            String oridata = stringBuilder.toString();
            JSONObject jsonObject = JSONObject.parseObject(oridata);
            String line = prettyJson(oridata);
            log.info("入参的切面json串:\r\n{}", line);

            if ("post".equals(httpMethod) && hasDecrypt) {

                /*
                 * 由于通过了Result加密后多了个data,所以再次加密后需要获取data的value后再进行加密
                 * {
                 *     "data": {
                 *         "rsaData": "FkvZ+vCiyJePoaXbZDfw++Jfgk7gJNAE9piaPOebH0YcaAF70q3GWracDSSiXG0FFKmzV/MGadl5rIyg3DA+Z9/ETDRCr6Z5SYSfL5FDmL0jTUIMxErvPEjYsTIk9MTur7J1/p4wVsTxkZprXUoc3fXdyshi6cM2kC+KuNxwGjA=",
                 *         "aesData": "K8pocSiGFUZYmmFgmFOD6iwcBBi8BBBclSyePKPd+pDUft4B/wmB5nk4sd9CWNCR3F36X2JVrOrqM2XIm+kX8Q=="
                 *     }
                 * }
                 **/

                String data = null;
                if (oridata.indexOf("data") != -1) {
                    data = jsonObject.getString("data");
                    jsonObject = JSONObject.parseObject(data);
                }
                //AES加密后的数据
                String aesKey = jsonObject.getString("aesData");

                //后端RSA公钥加密后的AES数据
                String dataString = jsonObject.getString("rsaData");

                //后端私钥解密的到AES的key
                byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(dataString), PRIVATEKEY);

                String rsaData = new String(plaintext);
                log.info("解密出来的AES生成的key为:{}", rsaData);

                String originData = AesUtil.decrypt(aesKey, rsaData);
                log.info("解密出来的data数据:\r\n{}", prettyJson(originData));

                if (args.length > 0) {
                    args[0] = JSONObject.parseObject(originData, args[0].getClass());
                }
            }

            Object object = pjp.proceed(args);
            if (hasEncrypt) {
               String  aesKey = AesUtil.getKey();
                log.info("AES的key:{}", aesKey);

                String  dataString = JSONObject.toJSONString(object);
                log.info("需要解密处理后再加密json数据:\r\n{}", prettyJson(dataString));

                dataString = JSONObject.parseObject(dataString).getString("data");
                log.info("把data对应的value提取出来的originData:\r\n{}", prettyJson(dataString));

                String aesData = AesUtil.encrypt(dataString, aesKey);
                log.info("AesUtil加密后的数据:{}", aesData);

                String rsaData = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(aesKey.getBytes(), PUBLICKEY));
                log.info("RsaUtil加密后的data数据:{}", rsaData);

                String originData = "{\"rsaData\": \"" + rsaData + "\",\"aesData\": \"" + aesData + "\"}";
                log.info("未Result化,返回给前端的加密数据为:\r\n{}", prettyJson(originData));
                object = Result.of(JSONObject.parseObject(originData, Object.class));
            }

            return object;
        } catch (Throwable e) {
            log.error(e.getMessage());
            return Result.of("加解密异常:\n\t" + e.getMessage());
        }
    }

    public static String prettyJson(String reqJson) {
        JSONObject object = JSONObject.parseObject(reqJson);
        return JSON.toJSONString(object, new SerializerFeature[]{SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat});
    }
}

这两个切面的区别比较少,不同的在返回值和对data的处理,同学们可以自己copy到开发工具中debug模式看看具体的区别。

测试

我是用的postman来调用的:
加密(调用的是切面1:SafetyAspect)


在这里插入图片描述

拿着加密的数据去解密(调用的是切面1:SafetyAspect)


切面1

解密后的数据处理后再加密(调用的是切面2:SafetyAspectPlus)


切面2

再对SafetyAspectPlus加密的结果用切面SafetyAspect来解密
同时调用

和第一次加密后用切面SafetyAspect解密的结果是一样的。
对应的日志:


日志1-1

日志1-2

日志1-3

这3张日志的截图是刚才在postman执行的日志纪录,是完整的日志记录。

到这里这篇博客就基本完成了,如果有更好的办法或建议欢迎留下足迹。

---------------------------------与君共勉-------------------------------------

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容