Spring Boot 实现请求参数与响应参数加解密

Spring Boot 实现请求参数与响应参数加解密

一、需求

只针对@RequestBody @ResponseBody两个注解起作用

期望在request请求进入controller前做是否解密验证,response在返回前做是否加密验证

二、设计

  • 添加自定义注解 EncryptResponse加密注解,DecryptRequest解密注解(使用范围类与方法上)
  • 添加一个加解密注解的判定类。
  • 继承RequestBodyAdvice重写beforeBodyWrite方法结合判定类与外部配置确认调用是否需要加解密
    ResponseBodyAdvice重写beforeBodyRead方法结合判定类与外部配置确认调用是否需要加解密

RequestBodyAdviceResponseBodyAdvice

image-20221021104754617

public interface RequestBodyAdvice {

	// 判断是否拦截(可以更精细化地进行判断是否拦截)
	boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType);

	// 进行请求前的拦截处理
	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

	// 进行请求后的拦截处理
	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

	// 对空请求体的处理
	@Nullable
	Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}

image-20221021105431561

public interface ResponseBodyAdvice<T> {

	// 判断是否拦截(可以更精细化地进行判断是否拦截)
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	// 进行响应前的拦截处理
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);

}

三、实现

时序图

1.自定义加解密的注解

/**
 * @author wanxianbo
 * @description 加了此注解的接口(true)将进行数据解密操作(post的body) 可以放在类上,可以放在方法上
 * @date 创建于 2022/10/21
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DecryptRequest {
    /**
     * 是否对body进行解密
     */
    boolean value() default true;
}
/**
 * @author wanxianbo
 * @description 加了此注解的接口(true)将进行数据加密操作可以放在类上,可以放在方法上
 * @date 创建于 2022/10/21
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface EncryptResponse {
    /**
     * 是否对body进行解密
     */
    boolean value() default true;
}

2.实现RequestBodyAdvice、ResponseBodyAdvice 接口

/**
 * @author wanxianbo
 * @description 请求数据接收处理类 <br>
 * 对加了@Decrypt的方法的数据进行解密操作<br>
 * 只对 @RequestBody 参数有效
 * @date 创建于 2022/10/21
 */
@ControllerAdvice(basePackages = "com.xkcoding")
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Value("${spring.crypto.request.decrypt.charset:UTF-8}")
    private String charset = "UTF-8";

    private static final byte[] KEYS = "12345678abcdefgh".getBytes(StandardCharsets.UTF_8);

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return NeedCrypto.needDecrypt(parameter) ? new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                AES aes = SecureUtil.aes(KEYS);
                String content = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName(charset));
                String decryptBody = aes.decryptStr(content);
                return new ByteArrayInputStream(decryptBody.getBytes(Charset.forName(charset)));
            }

            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        } : inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
/**
 * @author wanxianbo
 * @description 对加了@EncryptResponse的方法的数据进行加密操作
 * @date 创建于 2022/10/21
 */
@ControllerAdvice(basePackages = "com.xkcoding")
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Logger log = LoggerFactory.getLogger(EncryptResponseBodyAdvice.class);

    private static final byte[] KEYS = "12345678abcdefgh".getBytes(StandardCharsets.UTF_8);

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return NeedCrypto.needEncrypt(returnType);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 对body进行封装处理
        if (StringUtils.equals(returnType.getMethod().getReturnType().getName(), Void.TYPE.getName())) {
            return extract(ResponseEntity.ok(body), returnType);
        } else
        {
            if (body instanceof ResponseEntity) {
                return extract(body, returnType);
            }
        }

        return extract(ResponseEntity.ok(body), returnType);
    }

    private Object extract(Object body, MethodParameter methodParameter) {
        try {
            String content = objectMapper.writeValueAsString(body);
            AES aes = SecureUtil.aes(KEYS);
            return aes.encryptHex(content);
        } catch (Exception e) {
            log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:", e);
            throw new RuntimeException("加密 失败");
        }
    }
}
/**
 * @author wanxianbo
 * @description 判断是否需要加解密
 * @date 创建于 2022/10/21
 */
public class NeedCrypto {
    private NeedCrypto() {

    }

    /**
     * 是否需要参数加密
     * 1.类上标注或者方法上标注,并且都为true
     * 2.有一个标注为false就不需要解密
     *
     * @param methodParameter 参数
     * @return boolean
     */
    public static boolean needEncrypt(MethodParameter methodParameter) {
        boolean encrypt = false;
        boolean classPresentAnno = methodParameter.getContainingClass().isAnnotationPresent(EncryptResponse.class);
        boolean methodPresentAnno = Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(EncryptResponse.class);

        if (classPresentAnno) {
            // 类上标注是否需要解密
            encrypt = methodParameter.getContainingClass().getAnnotation(EncryptResponse.class).value();
            // 类上不加密,所有的都不加密
            return encrypt;
        }
        if (methodPresentAnno) {
            // 方法上标注是否需要解密
            encrypt = Objects.requireNonNull(methodParameter.getMethod()).getAnnotation(EncryptResponse.class).value();
        }
        return encrypt;
    }

    /**
     * 是否需要参数解密
     * 1.类上标注或者方法上标注,并且都为true
     * 2.有一个标注为false就不需要解密
     *
     * @param methodParameter 参数
     * @return boolean
     */
    public static boolean needDecrypt(MethodParameter methodParameter) {
        boolean encrypt = false;
        boolean classPresentAnno = methodParameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);
        boolean methodPresentAnno = Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(DecryptRequest.class);

        if (classPresentAnno) {
            // 类上标注是否需要解密
            encrypt = methodParameter.getContainingClass().getAnnotation(DecryptRequest.class).value();
            // 类上不加密,所有的都不加密
            return encrypt;
        }
        if (methodPresentAnno) {
            // 方法上标注是否需要解密
            encrypt = Objects.requireNonNull(methodParameter.getMethod()).getAnnotation(DecryptRequest.class).value();
        }
        return encrypt;
    }
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Spring AOP实现对接口请求响应参数加密。具体实现可以参考以下步骤: 1. 定义一个加密工具类,实现参数加密的逻辑。 2. 定义一个切面类,使用@Before和@AfterReturning注解分别在接口请求前和响应进行参数加密和解密。 3. 在Spring Boot的配置文件中配置切面类。 4. 在接口方法上添加@Encrypt和@Decrypt注解,指定需要加密和解密参数。 以下是一个简单的示例代码: 加密工具类: ``` public class EncryptUtils { public static String encrypt(String data) { // 实现加密逻辑 } public static String decrypt(String data) { // 实现解密逻辑 } } ``` 切面类: ``` @Aspect @Component public class EncryptAspect { @Before("@annotation(com.example.demo.annotation.Encrypt)") public void encryptRequest(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String encryptedData = EncryptUtils.encrypt((String) arg); // 替换原始参数 arg = encryptedData; } } } @AfterReturning(value = "@annotation(com.example.demo.annotation.Decrypt)", returning = "result") public void decryptResponse(Object result) { if (result instanceof String) { String decryptedData = EncryptUtils.decrypt((String) result); // 替换返回值 result = decryptedData; } } } ``` 配置文件: ``` @Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public EncryptAspect encryptAspect() { return new EncryptAspect(); } } ``` 接口方法: ``` @RestController public class UserController { @PostMapping("/user") @Encrypt @Decrypt public User createUser(@RequestBody User user) { // 处理业务逻辑 return user; } } ``` 以上代码仅供参考,实际实现需要根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值