前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【开发者必看】Spring Boot接口加密解密新姿势,技术干货,开箱即用!

【开发者必看】Spring Boot接口加密解密新姿势,技术干货,开箱即用!

作者头像
程序视点
发布于 2025-01-17 12:06:44
发布于 2025-01-17 12:06:44
21800
代码可运行
举报
文章被收录于专栏:程序小小事程序小小事
运行总次数:0
代码可运行

1. 前言

在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用。

如果我们想保证数据传输的安全,就需要对接口出参加密,入参解密。如果不想写重复代码,我们可以提供一个通用starter,提供通用加密解密功能!

今天我们就来聊一聊这个话题!

2. 前置知识

2.1 hutool-crypto加密解密工具

hutool-crypto提供了很多加密解密工具,包括对称加密非对称加密,摘要加密等等,这不做详细介绍。

2.2 request流只能读取一次的问题

2.2.1 问题:

在接口调用链中,request的请求流只能调用一次,处理之后,如果之后还需要用到请求流获取数据,就会发现数据为空。

比如使用了filter或者aop在接口处理之前,获取了request中的数据,对参数进行了校验,那么之后就不能再获取request请求流了

2.2.2 解决办法

继承HttpServletRequestWrapper,将请求中的流copy一份,复写getInputStreamgetReader方法供外部使用。

每次调用后的getInputStream方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在。

使用Filter过滤器,在一开始,替换request为自己定义的可以多次读取流的request。这样就实现了流的重复获取。

对以下代码实现或示例不清楚的小伙伴,可以安装、激活GitHub Copilot智能编程助手。本号提供全网唯一真正做到100%赔付保障的GitHub Copilot激活!

关注WX公众号【程序视点】,回复copilot,进行了解和订阅!

InputStreamHttpServletRequestWrapper

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.cryptotest.utils;  
  
import org.apache.commons.io.IOUtils;  
  
import javax.servlet.ReadListener;  
import javax.servlet.ServletInputStream;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletRequestWrapper;  
import java.io.BufferedReader;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.InputStreamReader;  
  
/**  
 * 请求流支持多次获取  
 */  
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {  
  
    /**  
     * 用于缓存输入流  
     */  
    private ByteArrayOutputStream cachedBytes;  
  
    public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {  
        super(request);  
    }  
  
    @Override  
    public ServletInputStream getInputStream() throws IOException {  
        if (cachedBytes == null) {  
            // 首次获取流时,将流放入 缓存输入流 中  
            cacheInputStream();  
        }  
  
        // 从 缓存输入流 中获取流并返回  
        return new CachedServletInputStream(cachedBytes.toByteArray());  
    }  
  
    @Override  
    public BufferedReader getReader() throws IOException {  
        return new BufferedReader(new InputStreamReader(getInputStream()));  
    }  
  
    /**  
     * 首次获取流时,将流放入 缓存输入流 中  
     */  
    private void cacheInputStream() throws IOException {  
        // 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtils  
        cachedBytes = new ByteArrayOutputStream();  
        IOUtils.copy(super.getInputStream(), cachedBytes);  
    }  
  
    /**  
     * 读取缓存的请求正文的输入流  
     * <p>  
     * 用于根据 缓存输入流 创建一个可返回的  
     */  
    public static class CachedServletInputStream extends ServletInputStream {  
  
        private final ByteArrayInputStream input;  
  
        public CachedServletInputStream(byte[] buf) {  
            // 从缓存的请求正文创建一个新的输入流  
            input = new ByteArrayInputStream(buf);  
        }  
  
        @Override  
        public boolean isFinished() {  
            return false;  
        }  
  
        @Override  
        public boolean isReady() {  
            return false;  
        }  
  
        @Override  
        public void setReadListener(ReadListener listener) {  
  
        }  
  
        @Override  
        public int read() throws IOException {  
            return input.read();  
        }  
    }  
  
}  

HttpServletRequestInputStreamFilter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.cryptotest.filter;  
  
import org.springframework.core.annotation.Order;  
import org.springframework.stereotype.Component;  
import xyz.hlh.cryptotest.utils.InputStreamHttpServletRequestWrapper;  
  
import javax.servlet.Filter;  
import javax.servlet.FilterChain;  
import javax.servlet.ServletException;  
import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
import javax.servlet.http.HttpServletRequest;  
import java.io.IOException;  
  
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;  
  
/**  
 * @author HLH  
 * @description:  
 *      请求流转换为多次读取的请求流 过滤器  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 9:58  
 */  
@Component  
@Order(HIGHEST_PRECEDENCE + 1)  // 优先级最高  
public class HttpServletRequestInputStreamFilter implements Filter {  
  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
  
        // 转换为可以多次获取流的request  
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;  
        InputStreamHttpServletRequestWrapper inputStreamHttpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(httpServletRequest);  
  
        // 放行  
        chain.doFilter(inputStreamHttpServletRequestWrapper, response);  
    }  
}  

2.3 SpringBoot的参数校验validation

为了减少接口中业务代码之前的大量冗余的参数校验代码,SpringBoot-validation提供了优雅的参数校验,入参都是实体类,在实体类字段上加上对应注解,就可以在进入方法之前,进行参数校验,如果参数错误,会抛出错误BindException,是不会进入方法的。

这种方法,必须要求在接口参数上加注解@Validated或者是@Valid。但是很多情况下,我们希望在代码中调用某个实体类的校验功能,所以需要如下工具类。

ParamException

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.cryptotest.exception;  
  
import lombok.Getter;  
  
import java.util.List;  
  
/**  
 * @author HLH  
 * @description 自定义参数异常  
 * @email 17703595860@163.com  
 * @date Created in 2021/8/10 下午10:56  
 */  
@Getter  
public class ParamException extends Exception {  
  
    private final List<String> fieldList;  
    private final List<String> msgList;  
  
    public ParamException(List<String> fieldList, List<String> msgList) {  
        this.fieldList = fieldList;  
        this.msgList = msgList;  
    }  
}

ValidationUtils

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.cryptotest.utils;  
  
import xyz.hlh.cryptotest.exception.CustomizeException;  
import xyz.hlh.cryptotest.exception.ParamException;  
  
import javax.validation.ConstraintViolation;  
import javax.validation.Validation;  
import javax.validation.Validator;  
import java.util.LinkedList;  
import java.util.List;  
import java.util.Set;  
  
/**  
 * @author HLH  
 * @description 验证工具类  
 * @email 17703595860@163.com  
 * @date Created in 2021/8/10 下午10:56  
 */  
public class ValidationUtils {  
  
    private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();  
  
    /**  
     * 验证数据  
     * @param object 数据  
     */  
    public static void validate(Object object) throws CustomizeException {  
  
        Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object);  
  
        // 验证结果异常  
        throwParamException(validate);  
    }  
  
    /**  
     * 验证数据(分组)  
     * @param object 数据  
     * @param groups 所在组  
     */  
    public static void validate(Object object, Class<?> ... groups) throws CustomizeException {  
  
        Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object, groups);  
  
        // 验证结果异常  
        throwParamException(validate);  
    }  
  
    /**  
     * 验证数据中的某个字段(分组)  
     * @param object 数据  
     * @param propertyName 字段名称  
     */  
    public static void validate(Object object, String propertyName) throws CustomizeException {  
        Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName);  
  
        // 验证结果异常  
        throwParamException(validate);  
  
    }  
  
    /**  
     * 验证数据中的某个字段(分组)  
     * @param object 数据  
     * @param propertyName 字段名称  
     * @param groups 所在组  
     */  
    public static void validate(Object object, String propertyName, Class<?> ... groups) throws CustomizeException {  
  
        Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName, groups);  
  
        // 验证结果异常  
        throwParamException(validate);  
  
    }  
  
    /**  
     * 验证结果异常  
     * @param validate 验证结果  
     */  
    private static void throwParamException(Set<ConstraintViolation<Object>> validate) throws CustomizeException {  
        if (validate.size() > 0) {  
            List<String> fieldList = new LinkedList<>();  
            List<String> msgList = new LinkedList<>();  
            for (ConstraintViolation<Object> next : validate) {  
                fieldList.add(next.getPropertyPath().toString());  
                msgList.add(next.getMessage());  
            }  
  
            throw new ParamException(fieldList, msgList);  
        }  
    }  
  
}  

2.4 自定义starter

自定义starter步骤

  • 创建工厂,编写功能代码
  • 声明自动配置类,把需要对外提供的对象创建好,通过配置类统一向外暴露
  • 在resource目录下准备一个名为spring/spring.factories的文件,以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key,自动配置类为value列表,进行注册

2.5 RequestBodyAdvice和ResponseBodyAdvice

  • RequestBodyAdvice是对请求的json串进行处理, 一般使用环境是处理接口参数的自动解密
  • ResponseBodyAdvice是对请求相应的jsoin传进行处理,一般用于相应结果的加密

3. 功能介绍

接口响应数据的时候,返回的是加密之后的数据;接口入参的时候,接收的是解密之后的数据,但是在进入接口之前,会自动解密,取得对应的数据

4. 功能细节

加密解密使用对称加密的AES算法,使用hutool-crypto模块进行实现所有的实体类提取一个公共父类,包含属性时间戳,用于加密数据返回之后的实效性,如果超过60分钟,那么其他接口将不进行处理。

如果接口加了加密注解EncryptionAnnotation,并且返回统一的json数据Result类,则自动对数据进行加密。

如果是继承了统一父类RequestBase的数据,自动注入时间戳,确保数据的时效性。

如果接口加了解密注解DecryptionAnnotation,并且参数使用RequestBody注解标注,传入json使用统一格式RequestData类,并且内容是继承了包含时间长的父类RequestBase,则自动解密,并且转为对应的数据类型功能提供Springboot的starter,实现开箱即用!

5. 代码实现

https://gitee.com/springboot-hlh/spring-boot-csdn/tree/master/09-spring-boot-interface-crypto

5.1 项目结构

5.2 crypto-common

5.2.1 结构

5.3 crypto-spring-boot-starter

5.3.1 接口
5.3.2 重要代码

crypto.properties AES需要的参数配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 模式    cn.hutool.crypto.Mode
crypto.mode=CTS
# 补码方式 cn.hutool.crypto.Mode
crypto.padding=PKCS5Padding
# 秘钥
crypto.key=testkey123456789
# 盐
crypto.iv=testiv1234567890

spring.factories 自动配置文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
        xyz.hlh.crypto.config.AppConfig  

CryptConfig AES需要的配置参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.config;  
  
import cn.hutool.crypto.Mode;  
import cn.hutool.crypto.Padding;  
import lombok.Data;  
import lombok.EqualsAndHashCode;  
import lombok.Getter;  
import org.springframework.boot.context.properties.ConfigurationProperties;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.PropertySource;  
  
import java.io.Serializable;  
  
/**  
 * @author HLH  
 * @description: AES需要的配置参数  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 13:16  
 */  
@Configuration  
@ConfigurationProperties(prefix = "crypto")  
@PropertySource("classpath:crypto.properties")  
@Data  
@EqualsAndHashCode  
@Getter  
public class CryptConfig implements Serializable {  
  
    private Mode mode;  
    private Padding padding;  
    private String key;  
    private String iv;  
  
}  

AppConfig 自动配置类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.config;  
  
import cn.hutool.crypto.symmetric.AES;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
import javax.annotation.Resource;  
import java.nio.charset.StandardCharsets;  
  
/**  
 * @author HLH  
 * @description: 自动配置类  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 13:12  
 */  
@Configuration  
public class AppConfig {  
  
    @Resource  
    private CryptConfig cryptConfig;  
  
    @Bean  
    public AES aes() {  
        return new AES(cryptConfig.getMode(), cryptConfig.getPadding(), cryptConfig.getKey().getBytes(StandardCharsets.UTF_8), cryptConfig.getIv().getBytes(StandardCharsets.UTF_8));  
    }  
  
}  

DecryptRequestBodyAdvice请求自动解密

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.advice;  
  
import com.fasterxml.jackson.databind.ObjectMapper;  
import lombok.SneakyThrows;  
import org.apache.commons.lang3.StringUtils;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.core.MethodParameter;  
import org.springframework.http.HttpInputMessage;  
import org.springframework.http.converter.HttpMessageConverter;  
import org.springframework.web.bind.annotation.ControllerAdvice;  
import org.springframework.web.context.request.RequestAttributes;  
import org.springframework.web.context.request.RequestContextHolder;  
import org.springframework.web.context.request.ServletRequestAttributes;  
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;  
import xyz.hlh.crypto.annotation.DecryptionAnnotation;  
import xyz.hlh.crypto.common.exception.ParamException;  
import xyz.hlh.crypto.constant.CryptoConstant;  
import xyz.hlh.crypto.entity.RequestBase;  
import xyz.hlh.crypto.entity.RequestData;  
import xyz.hlh.crypto.util.AESUtil;  
  
import javax.servlet.ServletInputStream;  
import javax.servlet.http.HttpServletRequest;  
import java.io.IOException;  
import java.lang.reflect.Type;  
  
/**  
 * @author HLH  
 * @description: requestBody 自动解密  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 15:12  
 */  
@ControllerAdvice  
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {  
  
    @Autowired  
    private ObjectMapper objectMapper;  
  
    /**  
     * 方法上有DecryptionAnnotation注解的,进入此拦截器  
     * @param methodParameter 方法参数对象  
     * @param targetType 参数的类型  
     * @param converterType 消息转换器  
     * @return true,进入,false,跳过  
     */  
    @Override  
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {  
        return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);  
    }  
  
    @Override  
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {  
        return inputMessage;  
    }  
  
    /**  
     * 转换之后,执行此方法,解密,赋值  
     * @param body spring解析完的参数  
     * @param inputMessage 输入参数  
     * @param parameter 参数对象  
     * @param targetType 参数类型  
     * @param converterType 消息转换类型  
     * @return 真实的参数  
     */  
    @SneakyThrows  
    @Override  
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {  
  
        // 获取request  
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;  
        if (servletRequestAttributes == null) {  
            throw new ParamException("request错误");  
        }  
  
        HttpServletRequest request = servletRequestAttributes.getRequest();  
  
        // 获取数据  
        ServletInputStream inputStream = request.getInputStream();  
        RequestData requestData = objectMapper.readValue(inputStream, RequestData.class);  
  
        if (requestData == null || StringUtils.isBlank(requestData.getText())) {  
            throw new ParamException("参数错误");  
        }  
  
        // 获取加密的数据  
        String text = requestData.getText();  
  
        // 放入解密之前的数据  
        request.setAttribute(CryptoConstant.INPUT_ORIGINAL_DATA, text);  
  
        // 解密  
        String decryptText = null;  
        try {  
            decryptText = AESUtil.decrypt(text);  
        } catch (Exception e) {  
            throw new ParamException("解密失败");  
        }  
  
        if (StringUtils.isBlank(decryptText)) {  
            throw new ParamException("解密失败");  
        }  
  
        // 放入解密之后的数据  
        request.setAttribute(CryptoConstant.INPUT_DECRYPT_DATA, decryptText);  
  
        // 获取结果  
        Object result = objectMapper.readValue(decryptText, body.getClass());  
  
        // 强制所有实体类必须继承RequestBase类,设置时间戳  
        if (result instanceof RequestBase) {  
            // 获取时间戳  
            Long currentTimeMillis = ((RequestBase) result).getCurrentTimeMillis();  
            // 有效期 60秒  
            long effective = 60*1000;  
  
            // 时间差  
            long expire = System.currentTimeMillis() - currentTimeMillis;  
  
            // 是否在有效期内  
            if (Math.abs(expire) > effective) {  
                throw new ParamException("时间戳不合法");  
            }  
  
            // 返回解密之后的数据  
            return result;  
        } else {  
            throw new ParamException(String.format("请求参数类型:%s 未继承:%s", result.getClass().getName(), RequestBase.class.getName()));  
        }  
    }  
  
    /**  
     * 如果body为空,转为空对象  
     * @param body spring解析完的参数  
     * @param inputMessage 输入参数  
     * @param parameter 参数对象  
     * @param targetType 参数类型  
     * @param converterType 消息转换类型  
     * @return 真实的参数  
     */  
    @SneakyThrows  
    @Override  
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {  
        String typeName = targetType.getTypeName();  
        Class<?> bodyClass = Class.forName(typeName);  
        return bodyClass.newInstance();  
    }  
}  

EncryptResponseBodyAdvice相应自动加密

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.advice;  
  
import cn.hutool.json.JSONUtil;  
import com.fasterxml.jackson.databind.ObjectMapper;  
import lombok.SneakyThrows;  
import org.apache.commons.lang3.StringUtils;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.core.MethodParameter;  
import org.springframework.http.MediaType;  
import org.springframework.http.ResponseEntity;  
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 sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;  
import xyz.hlh.crypto.annotation.EncryptionAnnotation;  
import xyz.hlh.crypto.common.entity.Result;  
import xyz.hlh.crypto.common.exception.CryptoException;  
import xyz.hlh.crypto.entity.RequestBase;  
import xyz.hlh.crypto.util.AESUtil;  
  
import java.lang.reflect.Type;  
  
/**  
 * @author HLH  
 * @description:  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 15:12  
 */  
@ControllerAdvice  
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Result<?>> {  
  
    @Autowired  
    private ObjectMapper objectMapper;  
  
    @Override  
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {  
        ParameterizedTypeImpl genericParameterType = (ParameterizedTypeImpl)returnType.getGenericParameterType();  
  
        // 如果直接是Result,则返回  
        if (genericParameterType.getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {  
            return true;  
        }  
  
        if (genericParameterType.getRawType() != ResponseEntity.class) {  
            return false;  
        }  
  
        // 如果是ResponseEntity<Result>  
        for (Type type : genericParameterType.getActualTypeArguments()) {  
            if (((ParameterizedTypeImpl) type).getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {  
                return true;  
            }  
        }  
  
        return false;  
    }  
  
    @SneakyThrows  
    @Override  
    public Result<?> beforeBodyWrite(Result<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {  
  
        // 加密  
        Object data = body.getData();  
  
        // 如果data为空,直接返回  
        if (data == null) {  
            return body;  
        }  
  
        // 如果是实体,并且继承了Request,则放入时间戳  
        if (data instanceof RequestBase) {  
            ((RequestBase)data).setCurrentTimeMillis(System.currentTimeMillis());  
        }  
  
        String dataText = JSONUtil.toJsonStr(data);  
  
        // 如果data为空,直接返回  
        if (StringUtils.isBlank(dataText)) {  
            return body;  
        }  
  
        // 如果位数小于16,报错  
        if (dataText.length() < 16) {  
            throw new CryptoException("加密失败,数据小于16位");  
        }  
  
        String encryptText = AESUtil.encryptHex(dataText);  
  
        return Result.builder()  
                .status(body.getStatus())  
                .data(encryptText)  
                .message(body.getMessage())  
                .build();  
    }  
}  

5.4 crypto-test

5.4.1 结构

5.4.2 重要代码application.yml 配置文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
spring:  
        mvc:  
        format:  
        date-time: yyyy-MM-dd HH:mm:ss  
        date: yyyy-MM-dd  
        # 日期格式化  
        jackson:  
        date-format: yyyy-MM-dd HH:mm:ss  

Teacher 实体类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.entity;  
  
import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.EqualsAndHashCode;  
import lombok.NoArgsConstructor;  
import org.hibernate.validator.constraints.Range;  
  
import javax.validation.constraints.NotBlank;  
import javax.validation.constraints.NotNull;  
import java.io.Serializable;  
import java.util.Date;  
  
/**  
 * @author HLH  
 * @description: Teacher实体类,使用SpringBoot的validation校验  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 10:21  
 */  
@Data  
@NoArgsConstructor  
@AllArgsConstructor  
@EqualsAndHashCode(callSuper = true)  
public class Teacher extends RequestBase implements Serializable {  
  
    @NotBlank(message = "姓名不能为空")  
    private String name;  
    @NotNull(message = "年龄不能为空")  
    @Range(min = 0, max = 150, message = "年龄不合法")  
    private Integer age;  
    @NotNull(message = "生日不能为空")  
    private Date birthday;  
  
}  

TestController 测试Controller

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.hlh.crypto.controller;  
  
import org.springframework.http.ResponseEntity;  
import org.springframework.validation.annotation.Validated;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestBody;  
import org.springframework.web.bind.annotation.RestController;  
import xyz.hlh.crypto.annotation.DecryptionAnnotation;  
import xyz.hlh.crypto.annotation.EncryptionAnnotation;  
import xyz.hlh.crypto.common.entity.Result;  
import xyz.hlh.crypto.common.entity.ResultBuilder;  
import xyz.hlh.crypto.entity.Teacher;  
  
/**  
 * @author HLH  
 * @description: 测试Controller  
 * @email 17703595860@163.com  
 * @date : Created in 2022/2/4 9:16  
 */  
@RestController  
public class TestController implements ResultBuilder {  
  
    /**  
     * 直接返回对象,不加密  
     * @param teacher Teacher对象  
     * @return 不加密的对象  
     */  
    @PostMapping("/get")  
    public ResponseEntity<Result<?>> get(@Validated @RequestBody Teacher teacher) {  
        return success(teacher);  
    }  
  
    /**  
     * 返回加密后的数据  
     * @param teacher Teacher对象  
     * @return 返回加密后的数据 ResponseBody<Result>格式  
     */  
    @PostMapping("/encrypt")  
    @EncryptionAnnotation  
    public ResponseEntity<Result<?>> encrypt(@Validated @RequestBody Teacher teacher) {  
        return success(teacher);  
    }  
  
    /**  
     * 返回加密后的数据  
     * @param teacher Teacher对象  
     * @return 返回加密后的数据 Result格式  
     */  
    @PostMapping("/encrypt1")  
    @EncryptionAnnotation  
    public Result<?> encrypt1(@Validated @RequestBody Teacher teacher) {  
        return success(teacher).getBody();  
    }  
  
    /**  
     * 返回解密后的数据  
     * @param teacher Teacher对象  
     * @return 返回解密后的数据  
     */  
    @PostMapping("/decrypt")  
    @DecryptionAnnotation  
    public ResponseEntity<Result<?>> decrypt(@Validated @RequestBody Teacher teacher) {  
        return success(teacher);  
    }  
  
}

好了!关于SpringBoot接口加解密的介绍就到这里了!有需要的小伙伴,可以直接上手试试!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序视点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Spring MVC ControllerAdvice深入解析
  Spring 在3.2版本后面增加了一个ControllerAdvice注解。网上的资料说的都是ControllerAdvice配合ExceptionHandler注解可以统一处理异常。而Spring MVC是如何做到的资料却比较少,下面会先给出使用的例子和踩过的一个坑。然后进行相应的源码分析,之后再介始ControllerAdvice另外的两种使用方式。
良辰美景TT
2018/09/11
1.5K0
第三十七章:基于SpringBoot架构以及参数装载完成接口安全认证
在上一章第三十六章:基于SpringBoot架构重写SpringMVC请求参数装载中我们说到了怎么去重写SpringMVC参数装载,从而来完成我们的需求。本章内容会在上一章的基础上进行修改! 企业中接口编写是再频繁不过的事情了,现在接口已经不仅仅用于移动端来做数据服务了,一些管理平台也同样采用了这种方式来完成前后完全分离的模式。不管是接口也好、分离模式也好都会涉及到数据安全的问题,那我们怎么可以很好的避免我们的数据参数暴露呢? 本章目标 基于SpringBoot平台实现参数安全传输。 SpringBoot
恒宇少年
2018/06/27
1.5K0
优美的统一返回结果处理
我们写项目一般都会自己写一个Result对象,然后去处理,但是有一个问题,逐渐的接口写多了之后
用户10136162
2022/11/15
7100
优美的统一返回结果处理
一文弄懂spring validate​
校验参数在以前基本都是使用大量的if/else,稍微方便一点的可以使用反射+自定义注解的形式,但是复用性不是很好,并且每个人对于的自定义注解有着自己的使用习惯,不过好在spring开发了validated框架用于注解校验,可以节省很多的校验ifelse代码,这篇文章通篇介绍了如何使用spring validated。
阿东
2021/08/16
3.8K0
SpringBoot 实现用户登录,分布式Session功能
之前介绍过不少关于登录功能的代码,本文介绍一下关于分布式Session 的功能实现,
Java帮帮
2020/02/11
4.3K0
SpringBoot 实现用户登录,分布式Session功能
拿去用,接口统一返回值,最简单的一种实现
上一篇中介绍了 RequestBodyAdvice 接口,可以对@RequestBody 进行增强,本文介绍另外一个相似的接口:ResponseBodyAdvice,这个可以对@ResponseBody 进行增强,可以拦截@ResponseBody 标注的方法的返回值,对返回值进行统一处理,比如进行加密、包装等操作;比如通过他可以实现统一的返回值。
路人甲Java
2021/10/20
1.3K0
Spring Boot 无侵入式 实现 API 接口统一 JSON 格式返回
无侵入式 统一返回JSON格式 其实本没有没打算写这篇博客的,但还是要写一下写这篇博客的起因是因为,现在呆着的这家公司居然没有统一的API返回格式?,询问主管他居然告诉我用HTTP状态码就够用了(fx
架构师修炼
2020/11/19
1.2K0
微信小程序之获取并解密用户数据(获取openid,nickName等)
本文主要总结微信小程序通过后台请求访问微信用户信息 创建一个微信小程序工程(自行百度) 微信小程序index.js代码 //index.js //获取应用实例 const app = getApp() Page({ data: { motto: 'Hello World', userInfo: {}, backUserInfo:{},//后台得到的微信用户信息 hasUserInfo: false, canIUse: wx.canIUse('button.o
小帅丶
2021/12/28
4.9K0
微信小程序之获取并解密用户数据(获取openid,nickName等)
Spring 使用 RequestBodyAdvice 来实现请求参数的加解密预处理
以上的解决方案都能解决我们的问题,这里不一一介绍每个方案是怎么实现的,主要讲一下 RequestBodyAdvice 的使用
双鬼带单
2021/04/13
5.4K0
Spring 使用 RequestBodyAdvice 来实现请求参数的加解密预处理
SpringBoot自定义注解实现Token校验
来源:blog.csdn.net/qq_33556185/article/details/105420205
Java小咖秀
2021/03/12
8580
Java使用拦截器的两种方式
可以看出来,这个不是真正意义上的拦截器,而只是功能来说是拦截数据(面向切面编程);
JQ实验室
2022/02/09
4400
springboot对返回值作统一处理方式
在使用springboot的使用,我们更加多的方式是返回json数据,直接返回,如下(比如返回一个对象):
用户7741497
2022/08/01
2.2K0
Springboot统一返回接口+统一异常处理+后端参数校验
引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </
用户5546570
2020/09/17
3.8K0
Springboot统一返回接口+统一异常处理+后端参数校验
自定义注解实现参数验证与业务代码解耦
核心依赖 <!-- aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId>
Meet相识
2018/09/12
1.3K0
VUE和Spring Boot 实现加密
采用请求前拦截操作,encrypt(JSON.stringify(config.data))
ruochen
2021/12/16
3.4K0
SpringMVC系列第18篇:强大的RequestBodyAdvice解密
在实际项目中,有时候我们需要在请求之前或之后做一些操作,比如:对参数进行解密,对所有的返回值进行加密等。这些与业务无关的操作,我们没有必要在每个 controller 方法中都写一遍,这里我们就可以使用 springmvc 中的@ControllerAdvice 和 RequestBodyAdvice、ResponseBodyAdvice 来对请求前后进行处理,本质上就是 aop 的思想。
路人甲Java
2021/10/20
2.9K0
基于 RequestBodyAdvice 与 ResponseBodyAdvice 实现统一加密和解密
在日常开发中,有时候经常需要和第三方接口打交道,有时候是我方调用别人的第三方接口,有时候是别人在调用我方的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余。那么有没有简单的方法,借助 spring 提供的 RequestBodyAdvice 和 ResponseBodyAdvice 可以实现解密和加密操作。
BUG弄潮儿
2021/06/25
1.7K0
基于 RequestBodyAdvice 与 ResponseBodyAdvice 实现统一加密和解密
Spring Boot REST 风格 API 接口 JWT Token 认证
1 能够有选择地过滤没有权限(Token)的请求 2 Token 具有时效性 3 如果用户连续操作,Token 能够自动刷新(自动延长有效期)
Java宝典
2021/01/14
1.7K0
SM4加密解密
https://baike.baidu.com/item/SM4.0/3901780
bug专8
2021/02/03
3.7K2
RequestBodyAdvice和注解方式进行统一参数处理demo
oktokeep
2024/10/09
580
推荐阅读
相关推荐
Spring MVC ControllerAdvice深入解析
更多 >
LV.3
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档