Java:SpringBoot使用AES对JSON数据加密和解密
作者:mmseoamin日期:2024-02-05

目录

    • 1、加密解密原理
    • 2、项目示例
      • 2.1、项目结构
      • 2.2、常规业务代码
      • 2.3、加密的实现
      • 2.4、接口测试
      • 2.5、总结

        1、加密解密原理

        客户端和服务端都可以加密和解密,使用base64进行网络传输

        加密方

        字符串 -> AES加密 -> base64
        

        解密方

        base64 -> AES解密 -> 字符串
        

        2、项目示例

        2.1、项目结构

        $ tree -I target -I test
        .
        ├── pom.xml
        └── src
            └── main
                ├── java
                │   └── com
                │       └── example
                │           └── demo
                │               ├── Application.java
                │               ├── annotation
                │               │   └── SecretData.java
                │               ├── config
                │               │   ├── CrossConfig.java
                │               │   ├── DecryptRequestBodyAdvice.java
                │               │   ├── EncryptResponseBodyAdvice.java
                │               │   ├── SecretConfig.java
                │               │   └── WebMvcConfig.java
                │               ├── controller
                │               │   ├── IndexController.java
                │               │   └── UserController.java
                │               ├── request
                │               │   └── JsonRequest.java
                │               ├── response
                │               │   ├── JsonResult.java
                │               │   └── JsonResultVO.java
                │               ├── service
                │               │   ├── SecretDataService.java
                │               │   └── impl
                │               │       └── SecretDataServiceImpl.java
                │               └── utils
                │                   └── CipherUtil.java
                └── resources
                    ├── application.yml
                    ├── static
                    │   ├── axios.min.js
                    │   └── crypto-js.min.js
                    └── templates
                        └── index.html
        

        2.2、常规业务代码

        pom.xml

        
        
            4.0.0
            
                org.springframework.boot
                spring-boot-starter-parent
                2.7.7
                 
            
            com.example
            demo
            0.0.1-SNAPSHOT
            demo
            Demo project for Spring Boot
            
                1.8
                3.5.2
            
            
                
                    org.springframework.boot
                    spring-boot-starter-web
                
                
                    commons-codec
                    commons-codec
                    1.15
                
                
                    org.springframework.boot
                    spring-boot-starter-thymeleaf
                
                
                    org.projectlombok
                    lombok
                    true
                
                
                    org.springframework.boot
                    spring-boot-devtools
                    runtime
                    true
                
                
                
                    org.springframework.boot
                    spring-boot-starter-test
                    test
                
                
                    junit
                    junit
                    test
                
            
            
                
                    
                        org.springframework.boot
                        spring-boot-maven-plugin
                        
                            
                                
                                    org.projectlombok
                                    lombok
                                
                            
                        
                    
                
            
        
        

        Application.java

        package com.example.demo;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        @SpringBootApplication
        public class Application {
            public static void main(String[] args) {
                SpringApplication.run(Application.class, args);
            }
        }
        

        WebMvcConfig.java

        package com.example.demo.config;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
        import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
        @Slf4j
        @Configuration
        public class WebMvcConfig extends WebMvcConfigurationSupport {
            // 设置静态资源映射
            @Override
            protected void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**")
                        .addResourceLocations("classpath:/static/");
            }
        }
        

        CrossConfig.java

        package com.example.demo.config;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.cors.CorsConfiguration;
        import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
        import org.springframework.web.filter.CorsFilter;
        /**
         * 处理跨域问题
         */
        @Configuration
        public class CrossConfig {
            @Bean
            public CorsFilter corsFilter() {
                CorsConfiguration config = new CorsConfiguration();
                config.addAllowedOrigin("*");
                config.setAllowCredentials(false);
                config.addAllowedMethod("*");
                config.addAllowedHeader("*");
                config.setMaxAge(3600L);
                UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
                configSource.registerCorsConfiguration("/**", config);
                return new CorsFilter(configSource);
            }
        }
        

        JsonRequest.java

        package com.example.demo.request;
        import lombok.Data;
        /**
         * 统一的请求体数据
         */
        @Data
        public class JsonRequest {
            /**
             * 未加密数据
             */
            private Object data;
            /**
             * 加密数据
             */
            private String encryptData;
        }
        

        JsonResult.java

        package com.example.demo.response;
        import lombok.Data;
        /**
         * 统一的返回体数据 不加密
         */
        @Data
        public class JsonResult {
            private String message;
            private T data;
            private Integer code;
            public static  JsonResult success(T data){
                JsonResult jsonResult = new JsonResult<>();
                jsonResult.setCode(0);
                jsonResult.setData(data);
                jsonResult.setMessage("success");
                return jsonResult;
            }
        }
        

        JsonResultVO.java

        package com.example.demo.response;
        import lombok.Data;
        /**
         * 统一的返回体数据 加密
         */
        @Data
        public class JsonResultVO {
            private String message;
            private String encryptData;
            private Integer code;
        }
        

        IndexController.java

        package com.example.demo.controller;
        import org.springframework.stereotype.Controller;
        import org.springframework.web.bind.annotation.GetMapping;
        @Controller
        public class IndexController {
            @GetMapping("/")
            public String index(){
                return "index";
            }
        }
        

        UserController.java

        package com.example.demo.controller;
        import com.example.demo.annotation.SecretData;
        import com.example.demo.response.JsonResult;
        import org.springframework.web.bind.annotation.*;
        import java.util.HashMap;
        import java.util.Map;
        /**
         * 对该Controller中的所有方法进行加解密处理
         */
        @RestController
        @SecretData
        public class UserController {
            @GetMapping("/user/getUser")
            public JsonResult getUser() {
                Map user = new HashMap<>();
                user.put("name", "Tom");
                user.put("age", "18");
                return JsonResult.success(user);
            }
            @PostMapping("/user/addUser")
            public Object addUser(@RequestBody Map data) {
                System.out.println(data);
                return data;
            }
        }
        

        2.3、加密的实现

        application.yml

        secret:
          key: 1234567890123456 # 密钥位数为16位
          enabled: true # 开启加解密功能
        

        SecretData.java

        package com.example.demo.annotation;
        import org.springframework.web.bind.annotation.Mapping;
        import java.lang.annotation.*;
        /**
         * 只有添加有该注解的Controller类或是具体接口方法才进行数据的加密解密
         */
        @Target({ElementType.METHOD, ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Mapping
        @Documented
        public @interface SecretData {
        }
        

        DecryptRequestBodyAdvice.java

        package com.example.demo.config;
        import com.example.demo.annotation.SecretData;
        import com.example.demo.request.JsonRequest;
        import com.example.demo.service.SecretDataService;
        import com.fasterxml.jackson.databind.ObjectMapper;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
        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 javax.annotation.Resource;
        import java.io.ByteArrayInputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.lang.reflect.Type;
        /**
         * 对请求内容进行解密
         * 只有开启了加解密功能才会生效
         * 仅对使用了@RqestBody注解的生效
         * https://blog.csdn.net/xingbaozhen1210/article/details/98189562
         */
        @ControllerAdvice
        // @ConditionalOnProperty(name = "secret.enabled", havingValue = "true")
        public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
            @Resource
            private SecretDataService secretDataService;
            @Override
            public boolean supports(MethodParameter methodParameter, Type targetType,
                                    Class> converterType) {
                return methodParameter.getMethod().isAnnotationPresent(SecretData.class)
                        || methodParameter.getMethod().getDeclaringClass().isAnnotationPresent(SecretData.class);
            }
            @Override
            public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                                   Class> converterType) throws IOException {
                System.out.println("beforeBodyRead");
                String body = inToString(inputMessage.getBody());
                System.out.println(body);
                ObjectMapper objectMapper = new ObjectMapper();
                JsonRequest jsonRequest = objectMapper.readValue(body, JsonRequest.class);
                // 默认取data数据,如果提交加密数据则解密
                String decryptData = null;
                if (jsonRequest.getEncryptData() != null) {
                    decryptData = secretDataService.decrypt(jsonRequest.getEncryptData());
                } else{
                    decryptData = objectMapper.writeValueAsString(jsonRequest.getData());
                }
                String data = decryptData;
                // 解密后的数据
                System.out.println(data);
                return new HttpInputMessage() {
                    @Override
                    public HttpHeaders getHeaders() {
                        return inputMessage.getHeaders();
                    }
                    @Override
                    public InputStream getBody() throws IOException {
                        return new ByteArrayInputStream(data.getBytes());
                    }
                };
            }
            /**
             * 读取输入流为字符串
             *
             * @param is
             * @return
             */
            private String inToString(InputStream is) {
                byte[] buf = new byte[10 * 1024];
                int length = -1;
                StringBuilder sb = new StringBuilder();
                try {
                    while ((length = is.read(buf)) != -1) {
                        sb.append(new String(buf, 0, length));
                    }
                    return sb.toString();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        

        EncryptResponseBodyAdvice.java

        package com.example.demo.config;
        import com.example.demo.annotation.SecretData;
        import com.example.demo.response.JsonResult;
        import com.example.demo.response.JsonResultVO;
        import com.example.demo.service.SecretDataService;
        import com.fasterxml.jackson.databind.ObjectMapper;
        import lombok.SneakyThrows;
        import org.springframework.beans.BeanUtils;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
        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;
        import javax.annotation.Resource;
        /**
         * 对响应内容加密
         */
        @ControllerAdvice
        @ConditionalOnProperty(name = "secret.enabled", havingValue = "true")
        public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {
            @Resource
            private SecretDataService secretDataService;
            @Override
            public boolean supports(MethodParameter returnType, Class> converterType) {
                return returnType.getMethod().isAnnotationPresent(SecretData.class)
                        || returnType.getMethod().getDeclaringClass().isAnnotationPresent(SecretData.class);
            }
            @SneakyThrows
            @Override
            public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                          Class> selectedConverterType,
                                          ServerHttpRequest request, ServerHttpResponse response) {
                System.out.println("beforeBodyWrite");
                // 仅对JsonResult对象数据加密
                if (body instanceof JsonResult) {
                    JsonResult jsonResult = (JsonResult) body;
                    JsonResultVO jsonResultVO = new JsonResultVO();
                    BeanUtils.copyProperties(jsonResult, jsonResultVO);
                    String jsonStr = new ObjectMapper().writeValueAsString(jsonResult.getData());
                    jsonResultVO.setEncryptData(secretDataService.encrypt(jsonStr));
                    return jsonResultVO;
                } else {
                    return body;
                }
            }
        }
         
        

        SecretConfig.java

        package com.example.demo.config;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import org.springframework.context.annotation.Configuration;
        @Configuration
        @ConfigurationProperties(prefix = "secret")
        public class SecretConfig {
            private Boolean enabled;
            private String key;
            public Boolean getEnabled() {
                return enabled;
            }
            public void setEnabled(Boolean enabled) {
                this.enabled = enabled;
            }
            public String getKey() {
                return key;
            }
            public void setKey(String key) {
                this.key = key;
            }
        }
        

        SecretDataService.java

        package com.example.demo.service;
        /**
         * 加密解密的接口
         */
        public interface SecretDataService {
            /**
             * 数据加密
             *
             * @param data 待加密数据
             * @return String 加密结果
             */
            String encrypt(String data);
            /**
             * 数据解密
             *
             * @param data 待解密数据
             * @return String 解密后的数据
             */
            String decrypt(String data);
        }
        

        SecretDataServiceImpl.java

        package com.example.demo.service.impl;
        import com.example.demo.config.SecretConfig;
        import com.example.demo.service.SecretDataService;
        import com.example.demo.utils.CipherUtil;
        import org.springframework.stereotype.Service;
        import javax.annotation.Resource;
        /**
         * 具体的加解密实现
         */
        @Service
        public class SecretDataServiceImpl implements SecretDataService {
            @Resource
            private SecretConfig secretConfig;
            @Override
            public String decrypt(String data) {
                return CipherUtil.decrypt(secretConfig.getKey(), data);
            }
            @Override
            public String encrypt(String data) {
                return CipherUtil.encrypt(secretConfig.getKey(), data);
            }
        }
        

        CipherUtil.java

        package com.example.demo.utils;
        import javax.crypto.Cipher;
        import javax.crypto.spec.SecretKeySpec;
        import java.nio.charset.Charset;
        import java.util.Base64;
        /**
         * 数据加密解密工具类
         * 加密后返回base64
         */
        public class CipherUtil {
            /**
             * 定义加密算法
             */
            private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
            /**
             * 解密
             *
             * @param secretKey
             * @param cipherText base64
             * @return
             */
            public static String decrypt(String secretKey, String cipherText) {
                // 将Base64编码的密文解码
                byte[] encrypted = Base64.getDecoder().decode(cipherText);
                try {
                    Cipher cipher = Cipher.getInstance(ALGORITHM);
                    SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
                    cipher.init(Cipher.DECRYPT_MODE, key);
                    return new String(cipher.doFinal(encrypted));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            /**
             * 加密
             *
             * @param secretKey
             * @param plainText base64
             * @return
             */
            public static String encrypt(String secretKey, String plainText) {
                try {
                    Cipher cipher = Cipher.getInstance(ALGORITHM);
                    SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(), "AES");
                    cipher.init(Cipher.ENCRYPT_MODE, key);
                    return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes(Charset.forName("UTF-8"))));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        

        浏览器中实现加密解密

        templates/index.html

        
        
          
            
            接口数据加密解密
          
          
            
            
            
            
            
            

        查看控制台

        2.4、接口测试