SpringBoot实现接口的RSA和AES加解密

RSA加密和AES加密使用了hutool工具包

依赖

<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.7.13</version>
</dependency>

加解密工具类

public class EncryptionUtil {

    // rsa秘钥
    private final static String PRIVATE_KEY = "...";

    // rsa公钥
    private final static String PUBLIC_KEY = "...";

    // 创建rsa加密对象 服务端公钥是不需要的,公钥提供给前端就行
    private final static RSA RSA = new RSA(PRIVATE_KEY, PUBLIC_KEY);

    /**
     * 获取加密返回
     */
    public static EncryptionResponse getEncodeResponse(String encryptionData) {
        // 生成随机aes秘钥
        byte[] key = getRandomKey();
        AES aes = SecureUtil.aes(key);
        // 加密aes key
        String aesKey = RSA.encryptBase64(key, KeyType.PrivateKey);
        // aes加密内容
        String encryptString = aes.encryptBase64(encryptionData);
        return new EncryptionResponse(aesKey, encryptString);
    }

    /**
     * 获取解密返回流
     */
    public static InputStream getDecodeInputStream(EncryptionRequest request) {
        String encryptionKey = request.getEncryptionKey();
        // rsa解密 获取aes秘钥
        byte[] decryptKey = RSA.decrypt(encryptionKey, KeyType.PrivateKey);
        // aes进行解密内容 获取输入流(方便整合springMVC)
        AES aes = SecureUtil.aes(decryptKey);
        byte[] decryptData = aes.decrypt(request.getEncryptionData());
        return IoUtil.toStream(decryptData);
    }

    /**
     * 随机生成aes的秘钥
     */
    private static byte[] getRandomKey() {
        return RandomUtil.randomString(16).getBytes();
    }
}

进行加密解密的一些对象准备

#配置
request:
  encode: true # 配置接口返回是否进行加密
  decode: true # 配置接口入参是否进行解密
// 两个实体对象,用来表示请求和返回结果
@Data
@AllArgsConstructor
public class EncryptionResponse {

    // rsa加密后的aes秘钥
    private String encryptionKey;

    // aes加密后的数据内容
    private String encryptionData;
}

@Data
public class EncryptionRequest {

    // 同response
    private String encryptionKey;

    private String encryptionData;
}

// 自定义注解,方法上有这个注解的进行加密和解密的操作
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityParameter {

    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;
}

SpringMvc的responseBody增强

// 进行加密的advice,@ConditionalOnProperty配置request.encode等于true的时候才进行加密
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(value = "request.encode", havingValue = "true")
public class EncodeResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        // 方法上有SecurityParameter注解的进行加密 根据需求可以在判断SecurityParameter注解中的outEncode是否为true
        return methodParameter.hasMethodAnnotation(SecurityParameter.class);
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        try {
            // 要支持接口返回string的话,response需要序列化成string对象,因为接口返回string和对象使用消息转换器不是一样的
            // 把返回结果序列化成json
            String dataJson = objectMapper.writeValueAsString(o);
            // 进行加密返回
            return EncryptionUtil.getEncodeResponse(dataJson);
        } catch (JsonProcessingException e) {
            log.error("json processing error", e);
        }
        return o;
    }
}

SpringMvc的requestBody增强

// 参数进行解密的方法 request.decode配置控制是否参数进行解密(这个是RequestBody注解的增强,参数必须要加这个注解)
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
@ConditionalOnProperty(value = "request.decode", havingValue = "true")
public class DecodeRequestAdvice implements RequestBodyAdvice {

    private final ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        // 判断方法是否需要进行解密
        return methodParameter.hasMethodAnnotation(SecurityParameter.class) && methodParameter.getMethodAnnotation(SecurityParameter.class).inDecode();
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        // 读取body对象,获取EncryptionRequest对象
        InputStream body = httpInputMessage.getBody();
        EncryptionRequest encryptionRequest = objectMapper.readValue(body, EncryptionRequest.class);
        if (encryptionRequest.getEncryptionKey() == null) {
            throw new IllegalArgumentException("encryption error,request is null");
        }
        // 转换成解密后的HttpInputMessage对象
        HttpInputMessage decryHttpMessage = new HttpInputMessage() {
            @Override
            public HttpHeaders getHeaders() {
                return httpInputMessage.getHeaders();
            }

            // 解密后的输入流
            @Override
            public InputStream getBody() {
                return EncryptionUtil.getDecodeInputStream(encryptionRequest);
            }
        };
        return decryHttpMessage;
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }
}

执行结果

// 测试
@RestController
public class DemoController {

    // string的接口返回值要在ResponseBodyAdvice中进行特殊处理,将对象转换成string对象
    // 正常业务中基本是会固定返回对象的,所以我这里就不做处理了
    @SecurityParameter
    @PostMapping("test")
    public DemoResponse demo() {
        return new DemoResponse("success");
    }

}

接口执行结果
在这里插入图片描述


版权声明:本文为weixin_45485300原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。