Spring接口入参出参的加密与解密

加密解密本身并不是难事,问题是在何时去处理?定义一个过滤器,将请求和响应分别拦截下来进行处理也是一个办法,这种方式虽然粗暴,但是灵活,因为可以拿到一手的请求参数和响应数据。不过 SpringMVC 中给我们提供了 ResponseBodyAdviceRequestBodyAdvice,利用这两个工具可以对请求和响应进行预处理,非常方便。

1、首先自定义加密与解密注解

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)
public @interface Encrypt {
}
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、其次,定义一个加密工具类,加密这块有多种方案可以选择,对称加密、非对称加密,其中对称加密又可以使用 AES、DES、3DES 等不同算法,这里我们使用 Java 自带的 Cipher 来实现AES对称加密算法

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtils {

    // 定义加密算法
    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";

    // 获取 cipher
    private 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 String decrypt(byte[] data, byte[] key) throws Exception {
        Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
        return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
    }

    /**
     * 方便测试(正常情况下是前端加密请求参数,后端接收到请求参数进行解密操作,执行完逻辑之后,对返回的数据进行加密返回给前端,前端解密获取数据)
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        //String encryptKey = "1234567890123456";
        String encryptKey = "1111111111111111";

        String a = "{\"id\":\"1\",\"name\":\"tom\",\"age\":18}";
        String encrypt = encrypt(a.getBytes(), encryptKey.getBytes());
        System.out.println("【加密之后数据:】" + encrypt.replaceAll("\\r\\n", ""));

        String b = "sVEcp4uhNCW3LdTeCEUJxDkjppxKuKItagNSRYO5jRzzDGnE+UXmVOvUs4ixyPeQ";
        String decrypt = decrypt(b.getBytes(), encryptKey.getBytes());
        System.out.println("【解密之后数据:】" + decrypt);
    }

}

所有准备工作做完了,接下来就该正式加解密了。

在这里和大家分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,RequestBodyAdvice 在做解密的时候倒是没啥问题,而 ResponseBodyAdvice 在做加密的时候则会有一些局限,不过影响不大,如果想非常灵活的掌控一切,那还是自定义过滤器吧。

另外还有一点需要注意,ResponseBodyAdvice 在你使用了 @ResponseBody 注解的时候才会生效,RequestBodyAdvice 在你使用了 @RequestBody 注解的时候才会生效,换言之,前后端都是 JSON 交互的时候,这两个才有用。

先来看接口加密:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hihonor.behavior.analysis.pojo.vo.common.BaseResView;
import org.springframework.beans.factory.annotation.Value;
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;

/**
 * 接口加密
 */
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<BaseResView> {

    @Value("${spring.encrypt.key:1234567890123456}")
    private String encryptKey;

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

    @Override
    public BaseResView beforeBodyWrite(BaseResView body,
                                       MethodParameter returnType,
                                       MediaType selectedContentType,
                                       Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                       ServerHttpRequest request,
                                       ServerHttpResponse response) {

        System.out.println("=======================>>>>>>>>>>>>>>>>>>>>>:" + encryptKey);

        try {
            if (body.getDesc() != null) {
                body.setDesc(AESUtils.encrypt(body.getDesc().getBytes(), encryptKey.getBytes()));
            }
            if (body.getData() != null) {
                body.setData(AESUtils.encrypt(new ObjectMapper().writeValueAsBytes(body.getData()), encryptKey.getBytes()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return body;
    }

}

再是接口解密:

import org.springframework.beans.factory.annotation.Value;
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;

/**
 * 接口解密
 */
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {

    @Value("${spring.encrypt.key:1234567890123456}")
    private String encryptKey;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

        System.out.println("=======================>>>>>>>>>>>>>>>>>>>>>:" + encryptKey);

        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);
        try {
            String decrypt = AESUtils.decrypt(body, encryptKey.getBytes());
            final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt.getBytes());
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return bais;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }

}

如果想要修改加密密钥,可以在 application.properties 中添加如下配置:

spring:
  encrypt:
    key: 1111111111111111
@Value("${spring.encrypt.key:1234567890123456}")
private String encryptKey;

 @Value注解是Spring框架中的一种注解,用于将配置文件中的值注入到Java类中的字段中。

在这个例子中,@Value注解用于将一个名为spring.encrypt.key的配置值注入到encryptKey字段中。如果spring.encrypt.key在配置文件中没有被定义,那么默认值1234567890123456将被使用

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @GetMapping("/test1")
    @Encrypt
    public Result getUser() {
        User user = new User();
        user.setId(1);
        user.setName("tom");
        user.setAge(18);
        return Result.ok(user);
    }

    @PostMapping("/test2")
    public Result addUser(@Decrypt @RequestBody User user) {
        System.out.println("user = " + user);
        return Result.ok(user);
    }

}

这里的 Result 为封装好的统一结果集。

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Result {

    private Integer code;
    private String desc;
    private Object Data;

    public static Result ok(String desc) {
        return new Result(200, desc, null);
    }

    public static Result ok(String desc, Object obj) {
        return new Result(200, desc, obj);
    }

    public static Result error(String desc) {
        return new Result(500, desc, null);
    }

    public static Result error(String desc, Object obj) {
        return new Result(500, desc, obj);
    }

}

最后,正式开始我们的测试环节

test1:可以看到返回的出参已经加密

test2:可以看出请求体正常解密且返回数据

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值