【springboot系列】自定义注解实现加解密

该博客介绍了如何在Spring Boot应用中实现请求参数的AES解密和响应结果的AES加密。通过定义自定义注解、编写工具类、配置属性以及使用AOP思想,实现了数据的安全传输。示例展示了在Controller中使用解密和加密注解的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前提

引入依赖

 //web
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided</scope>
        </dependency>
//lombok
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

1、定义注解

加密注解类Encrypt

package com.walker.encrypt.anotation;

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

/**
* 加密注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {
}

解密注解类

package com.walker.encrypt.anotation;

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

/**
* 解密注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}

2、编写工具类

package com.walker.encrypt.util;

import com.sun.media.sound.FFT;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class AESUtil {
    private final static String AES="AES";
    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";


    /**
    * 获取cipher
    */
    private static Cipher getCipher(byte[] key,int model) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
        SecretKeySpec aes = new SecretKeySpec(key, AES);
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(model,aes);
        return cipher;
    }

    /**
    * 加密
    */
    public static String encrypt(byte[] data,byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data));
    }

    /**
    * 解密
    */
    public static byte[] decrypt(byte[] data,byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
        return cipher.doFinal(Base64.getDecoder().decode(data));
    }


}

3、配置密钥和密钥类

1、application.properties

#长度为16位
body.encrypt.key=iamwalkerencrypt

2、属性类

package com.walker.encrypt.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "body.encrypt")
@Data
public class EncryptProperties {
    private String key;
}

4、相关类

1、响应类

package com.walker.encrypt.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
    private Integer code;
    private String msg;
    private Object data;

    public static R ok(String msg){
        return new R(200,msg,null);
    }

    public static R ok(String msg,Object data){
        return new R(200,msg,data);
    }

    public static R error(String msg){
        return new R(500,msg,null);
    }

    public static R error(String msg,Object data){
        return new R(500,msg,data);
    }

}

2、用户类 【主要用于测试】

5、配置类

解密请求参数类

package com.walker.encrypt.config;

import com.walker.encrypt.anotation.Decrypt;
import com.walker.encrypt.properties.EncryptProperties;
import com.walker.encrypt.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

//允许获取EncryptProperties配置类
@EnableConfigurationProperties(EncryptProperties.class)
//@ControllerAdvice是一个全局数据处理组件
@ControllerAdvice

/**
* 实现的不是RequestBodyAdvice,而是RequestBodyAdviceAdapter
 * RequestBodyAdviceAdapter继承RequestBodyAdvice
*/
public class DecryptRequest extends RequestBodyAdviceAdapter {

    //引入加密属性类 主要是为了获取配置文件中的密钥
    @Autowired
    private EncryptProperties encryptProperties;


    /**
    * 配置支持条件
     * 这里是只有方法或者参数有Decrypt注解的时候才生效
    */
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasMethodAnnotation(Decrypt.class)||methodParameter.hasParameterAnnotation(Decrypt.class);
    }


    /**
    * 使用aop,在读取请求参数的之前,对请求参数进行解密
    */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

        //获取密钥的字节
        byte[] keyByte = encryptProperties.getKey().getBytes();
        //读取请求参数成为字节body
        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);


        try {
            //解密
            byte[] decrypt = AESUtil.decrypt(body, keyByte);
            //将解密的字节放入字节数组输入流中
            ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);

            //返回HttpInputMessage
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return bais;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }

        //如果不需要进行处理的话,则直接调用父类的beforeBodyRead,相当于直接返回inputMessage,不做处理
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}
package com.walker.encrypt.config;

import com.walker.encrypt.anotation.Decrypt;
import com.walker.encrypt.properties.EncryptProperties;
import com.walker.encrypt.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice

/**
* 实现的不是RequestBodyAdvice,而是RequestBodyAdviceAdapter
 * RequestBodyAdviceAdapter继承RequestBodyAdvice
*/
public class DecryptRequest extends RequestBodyAdviceAdapter {

    //引入加密属性类 主要是为了获取配置文件中的密钥
    @Autowired
    private EncryptProperties encryptProperties;


    /**
    * 配置支持条件
     * 这里是只有方法或者参数有Decrypt注解的时候才生效
    */
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasMethodAnnotation(Decrypt.class)||methodParameter.hasParameterAnnotation(Decrypt.class);
    }


    /**
    * 使用aop,在读取请求参数的之前,对请求参数进行解密
    */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

        //获取密钥的字节
        byte[] keyByte = encryptProperties.getKey().getBytes();
        //读取请求参数成为字节body
        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);


        try {
            //解密
            byte[] decrypt = AESUtil.decrypt(body, keyByte);
            //将解密的字节放入字节数组输入流中
            ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);
            
            //返回HttpInputMessage
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return bais;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }

        //如果不需要进行处理的话,则直接调用父类的beforeBodyRead,相当于直接返回inputMessage,不做处理
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}

加密返回结果类

package com.walker.encrypt.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.walker.encrypt.anotation.Encrypt;
import com.walker.encrypt.model.R;
import com.walker.encrypt.properties.EncryptProperties;
import com.walker.encrypt.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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;

//允许获取EncryptProperties配置类
@EnableConfigurationProperties(EncryptProperties.class)
//开启ControllerAdvice
@ControllerAdvice

//实现ResponseBodyAdvice
public class EncrptResponse implements ResponseBodyAdvice<R> {

    @Autowired
    private EncryptProperties encryptProperties;

    //用来Object对象的转换
    private ObjectMapper om=new ObjectMapper();


    /**
    * 支持什么时候加密
    */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasMethodAnnotation(Encrypt.class);
    }


    /**
    * 数据响应进行加密
     */
    @Override
    public R beforeBodyWrite(R r, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //获取key的字节
        byte[] keybytes = encryptProperties.getKey().getBytes();

        //如果msg和data存在的话,则进行加密,最后进行返回
        try {
            if(r.getMsg()!=null){
                r.setMsg(AESUtil.encrypt(r.getMsg().getBytes(),keybytes));
            }
            if(r.getData()!=null){
                r.setData(AESUtil.encrypt(om.writeValueAsBytes(r.getData()),keybytes));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return r;
    }
}

6、编写controller

package com.walker.encrypt.cotroller;

import com.walker.encrypt.anotation.Decrypt;
import com.walker.encrypt.anotation.Encrypt;
import com.walker.encrypt.model.R;
import com.walker.encrypt.model.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/encrypt")
public class EncryptController {


    @Encrypt
    @GetMapping("/encrypt")
    public R encryptTest(){
        User user = new User();
        user.setName("walker");
        user.setAge(18);

        return R.ok("请求成功",user);
    }



    /**
    * 将前面返回的参数拿来测试
     * QXnfSrsDbFPR7tBfiPEsqMs58zFa94H6lnv4LnrHcfE=
    */
    @PostMapping("/decrypt")
    public R decrypt(@RequestBody @Decrypt User user){
        return R.ok("ok",user);
    }
}

7、测试

1、返回结果加密

image.png

2、请求参数解密

image.png

原理

本质用的还是AOP的思想

参考文档:https://mp.weixin.qq.com/s/xq9bmpLJw6aqttTPyq_omA

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WalkerShen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值