接口加解密及数据加解密

目录

一、 加解密方式介绍

1.1 Hash算法加密

1.2. 对称加密

1.3 非对称加密

二、 我们要讲什么?

三、 接口加解密

四、 数据加解密


一、 加解密方式介绍

        所有的加密方式我们可以分为三类:对称加密、非对称加密、Hash算法加密。

        算法内部的具体实现见:(我们只是算法的搬运工)13种加密与解密算法【一】-CSDN博客

1.1 Hash算法加密

        这种加密方式是一种不可逆的加密方式,它通常用于验证数据的完整性(例如密码)。

        例如:MD5(严格的不可逆、单向加密、加盐)、Base64(严格说是一种编码转换、可以加解密)

1.2. 对称加密

        这种加密方式就是在加密和解密的过程中都使用了一个相同的秘钥(不妨我们将其记作password(和密码同名-_-))。那么我们对数据的加密、解密都使用这个秘钥。特点是加解密速度快。缺点是被人知道秘钥就GG。

        这种加密方式的算法例如:DES、AES、SM4(国密4)……

1.3 非对称加密

        在这种加密的方式中会出现密钥对,包括一个私钥和一个公钥。其中公钥和私钥无关(不能相互推出)。而实际上在双方之间进行数据的加密传输,在每一方自己的操作中进行数据的解密,会涉及到两对秘钥对。

        我们看这个图就很清楚。我们将甲方比作是后端,乙方比作是前端。 后端会有一对密钥对,同理前端也会有一对密钥对。需要注意的是我们的公钥是可以被公布的(和私钥无关),即我们的数据被截获也无法解密。这样一来,以后端为例,后端持有前端的公钥,当后端对前端返回数据的时候,后端使用前端的公钥对数据进行加密。从而前端获取到数据后使用自己的私钥进行解密。同理,前端给后端的数据也会使用后端的公钥进行加密,后端接受到数据后,就会使用后端的私钥进行解密。从而实现前后端之间数据的加密传输。

        这种非对称加密方式的算法有:RSA、SM2(国密2)……

二、 我们要讲什么?

        我们先从前端给的一个请求说起。前端想要对着接口给后端一些数据从而让后端完成业务处理和持久化。很好,好比一个登录注册的请求,后端完成了这方面的接口。前端也对着接口发送数据,然后被恶意人员截获,好啊,我们的数据都是明文,恶意人员又把这个请求放走,完成我们的业务逻辑与持久化。那么其实我们的这个账号就十分危险,其他方面也各种意义的危险。

        所以从这里,我们不希望前后端之间的数据被其他人(各种其他人)所看见,就是某种意义上不能裸奔!所以前端给后端发的数据应该是被加密过的,并且我们需要保证后端能够处理这段加密的数据。那么同理,后端完成业务逻辑与持久化之后,返回给前端的响应也不能裸奔,也需要进行加密,并且保证前端可以处理被加密的数据。

        那么如果我们使用对称加密,恶意人员各种操作,总之搞到了我们的秘钥。其实还是裸奔。所以我们回去使用非对称加密。

        好!继续说上边的,前端给后端加密后的数据,在后端里我们先对数据进行了解密,从而到达我们的业务逻辑处理。然后后端调用Mapper完成持久化,存储到数据库中。现在又有一个技术高超的恶意人员黑到了我们的数据库。哈哈,你的数据都是裸奔。这也就很不安全,各种意义上的危险。

        于是乎,后端在调用Mapper的时候,也应该对必要的字段进行加密,所以后端会有个拦截器去看看:“这家伙要存储的东西有没有是要加密的?”。然后对数据加密之后存储到数据库中。那么拿出来的时候也一样,后端还有个拦截器,看看:“有没有数据是被加密的?”。之后对数据解密,从而完成业务逻辑处理。

三、 接口加解密

        我们讲解ruoyi项目中如下文件。

        Request流只能读取一次?

package org.dromara.common.encrypt.annotation;import java.lang.annotation.*;/*** 强制加密注解** @author Michelle.Chung*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiEncrypt {// 最终实现 ApiEncrypt 的注解,用于加在 Controller 的方法上,指定是否对请求进行解密,对响应进行加密/*** 响应加密忽略,默认不加密,为 true 时加密*/boolean response() default false;}
package org.dromara.common.encrypt.config;import jakarta.servlet.DispatcherType;
import org.dromara.common.encrypt.filter.CryptoFilter;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;/*** api 解密自动配置** @author wdhcr*/
@AutoConfiguration
@EnableConfigurationProperties(ApiDecryptProperties.class) // 获取接口加解密相关配置信息
@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
public class ApiDecryptAutoConfiguration {@Bean // 注册过滤器 Filterpublic FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();registration.setDispatcherTypes(DispatcherType.REQUEST);// 设置过滤器并且将配置类传给过滤器registration.setFilter(new CryptoFilter(properties));// 对全部路径进行过滤registration.addUrlPatterns("/*");registration.setName("cryptoFilter");// 优先级最高registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);return registration;}
}
package org.dromara.common.encrypt.filter;import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import java.io.IOException;/*** Crypto 过滤器** @author wdhcr*/
public class CryptoFilter implements Filter { // 实现过滤器,重写其 init()、doFilter()、destroy() 方法private final ApiDecryptProperties properties;public CryptoFilter(ApiDecryptProperties properties) {this.properties = properties;}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 强转请求和响应HttpServletRequest servletRequest = (HttpServletRequest) request;HttpServletResponse servletResponse = (HttpServletResponse) response;// 这里是封装了一个方法,用来 从请求中获取到注解,判断是否为加密请求,如果是,那么就返回注解,否则返回 null// 那么如果 不是加密请求,null, 就会跳过,直接放行// 是的话 就会返回注解,然后进行解密ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);// apiEncrypt 不为空 说明 存在加密注解,那么就进行解密// 且 响应加密,那么 responseFlag 为 trueboolean responseFlag = apiEncrypt != null && apiEncrypt.response();// 新建请求和响应的包装类ServletRequest requestWrapper = null;ServletResponse responseWrapper = null;EncryptResponseBodyWrapper responseBodyWrapper = null;/***  对请求的数据进行解密*  对响应的数据进行加密*/// 这里需要说明一下,就是标识头是被 AES 加密过的// 首先看看是不是请求// 是否为 put 或者 post 请求,换句话说,DELETE 和 GET方法不用解密if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {// 是否存在加密标头,这个就说明前端传来了密文String headerValue = servletRequest.getHeader(properties.getHeaderFlag());if (StringUtils.isNotBlank(headerValue)) {// 请求解密,将请求,后端的私钥,以及加密标头放入,进行解密requestWrapper = new DecryptRequestBodyWrapper(servletRequest,properties.getPrivateKey(), properties.getHeaderFlag());} else {// 就是说我们是没有看到加密标头,但是又存在加密注解,那么就抛出异常if (ObjectUtil.isNotNull(apiEncrypt)) {// 获取 异常处理器, 然后抛出异常HandlerExceptionResolver exceptionResolver =SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);exceptionResolver.resolveException(servletRequest, servletResponse, null,new ServiceException("没有访问权限,请联系管理员授权", HttpStatus.FORBIDDEN));return;}// 没有加密注解,没有加密标头,那么直接放行这个请求// 其实到这里 对请求来说 就结束了,因为没有加密注解,所以 responseFlag 为 false,直接放行}}// 判断是否响应加密if (responseFlag) {// 对应的响应内容进行包装加密// 这一步只是 对 response 进行包装,并没有对 response 进行加密,因为 下边还有判断 是不是直接放行responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);responseWrapper = responseBodyWrapper;}// 如果 requestWrapper 和 responseWrapper 都为空,那么直接放行chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request),ObjectUtil.defaultIfNull(responseWrapper, response));// 不为空且响应加密,那么对 responseBodyWrapper 进行加密处理if (responseFlag) {// 重置 responseservletResponse.reset();// 对原始内容加密,将响应,后端的公钥,以及加密标头放入,进行加密String encryptContent = responseBodyWrapper.getEncryptContent(servletResponse, properties.getPublicKey(), properties.getHeaderFlag());// 对加密后的内容写出servletResponse.getWriter().write(encryptContent);}}/*** 获取 ApiEncrypt 注解*/private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);// 获取注解try {HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);if (ObjectUtil.isNotNull(mappingHandler)) {Object handler = mappingHandler.getHandler();if (ObjectUtil.isNotNull(handler)) {// 从handler获取注解if (handler instanceof HandlerMethod handlerMethod) {return handlerMethod.getMethodAnnotation(ApiEncrypt.class);}}}} catch (Exception e) {throw new RuntimeException(e);}return null;}@Overridepublic void destroy() {}
}
package org.dromara.common.encrypt.filter;import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.springframework.http.MediaType;import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;/*** 解密请求参数工具类** @author wdhcr*/
public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {// 存储请求体,以便能够重复读取private final byte[] body;public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {super(request);// 以下的操作使得最终 被加密的body 被解密出来// body 是通过 AES 加密// 只有我们获得了 AES 密码, 我们才可以 解密 body// 根据请求头的 headerFlag 获取到 RSA 加密后的 AES 密码String headerRsa = request.getHeader(headerFlag);// 将 AES 密码从 RSA 解密出来String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);// 将 AES 密码从 Base64 解密出来String aesPassword = EncryptUtils.decryptByBase64(decryptAes);// 设置 请求的编码格式request.setCharacterEncoding(Constants.UTF8);// 读取请求体byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);String requestBody = new String(readBytes, StandardCharsets.UTF_8);// 将 body 从 AES 解密出来String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);body = decryptBody.getBytes(StandardCharsets.UTF_8);}@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic int getContentLength() {return body.length;}@Overridepublic long getContentLengthLong() {return body.length;}@Overridepublic String getContentType() {return MediaType.APPLICATION_JSON_VALUE;}@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() {return bais.read();}@Overridepublic int available() {return body.length;}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}
}
package org.dromara.common.encrypt.filter;import cn.hutool.core.util.RandomUtil;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.dromara.common.encrypt.utils.EncryptUtils;import java.io.*;
import java.nio.charset.StandardCharsets;/*** 加密响应参数包装类** @author Michelle.Chung*/
public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {private final ByteArrayOutputStream byteArrayOutputStream;private final ServletOutputStream servletOutputStream;private final PrintWriter printWriter;public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {super(response);// 新建一个输出流this.byteArrayOutputStream = new ByteArrayOutputStream();this.servletOutputStream = this.getOutputStream();this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));}@Overridepublic PrintWriter getWriter() {return printWriter;}@Overridepublic void flushBuffer() throws IOException {if (servletOutputStream != null) {servletOutputStream.flush();}if (printWriter != null) {printWriter.flush();}}@Overridepublic void reset() {byteArrayOutputStream.reset();}public byte[] getResponseData() throws IOException {flushBuffer();return byteArrayOutputStream.toByteArray();}public String getContent() throws IOException {flushBuffer();return byteArrayOutputStream.toString();}/*** 获取加密内容** @param servletResponse response* @param publicKey       RSA公钥 (用于加密 AES 秘钥)* @param headerFlag      请求头标志* @return 加密内容* @throws IOException*/public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {// 生成秘钥String aesPassword = RandomUtil.randomString(32);// 秘钥使用 Base64 编码String encryptAes = EncryptUtils.encryptByBase64(aesPassword);// Rsa 公钥加密 Base64 编码String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);// 设置响应头servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);servletResponse.setHeader(headerFlag, encryptPassword);servletResponse.setHeader("Access-Control-Allow-Origin", "*");servletResponse.setHeader("Access-Control-Allow-Methods", "*");servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());// 获取原始内容String originalBody = this.getContent();// 对内容进行加密return EncryptUtils.encryptByAes(originalBody, aesPassword);}@Overridepublic ServletOutputStream getOutputStream() throws IOException {return new ServletOutputStream() {@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {}@Overridepublic void write(int b) throws IOException {byteArrayOutputStream.write(b);}@Overridepublic void write(byte[] b) throws IOException {byteArrayOutputStream.write(b);}@Overridepublic void write(byte[] b, int off, int len) throws IOException {byteArrayOutputStream.write(b, off, len);}};}}

四、 数据加解密

        我们讲解ruoyi中如下文件:

package org.dromara.common.encrypt.annotation;import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;import java.lang.annotation.*;/*** 字段加密注解** @author 老马*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField { // 最终我们就是在字段上加这个注解,然后通过反射获取到注解信息,然后对字段进行加密解密/*** 加密算法*/AlgorithmType algorithm() default AlgorithmType.DEFAULT;/*** 秘钥。AES、SM4需要*/String password() default ""; // 处理对称加密的秘钥// 下边两个是非对称加密的秘钥/*** 公钥。RSA、SM2需要*/String publicKey() default "";/*** 私钥。RSA、SM2需要*/String privateKey() default "";// 编码方式/*** 编码方式。对加密算法为BASE64的不起作用*/EncodeType encode() default EncodeType.DEFAULT;}
package org.dromara.common.encrypt.config;import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.encrypt.core.EncryptorManager;
import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
import org.dromara.common.encrypt.properties.EncryptorProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;/*** 加解密配置** @author 老马* @version 4.6.0*/
@AutoConfiguration(after = MybatisPlusAutoConfiguration.class) // 需在 MybatisPlusAutoConfiguration 之后加载// 使用这个注解会自动注入到 IoC 容器
@EnableConfigurationProperties(EncryptorProperties.class) // 加载配置文件
@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true") // 只有配置了// mybatis-encryptor.enable=true 时才// 会创建 Bean
@Slf4j
public class EncryptorAutoConfiguration {@Autowired // 获取配置文件,成为 Beanprivate EncryptorProperties properties;// 注入 EncryptorManager 加密管理器// 当项目一启动,就会把 EncryptorManager 加载到 IoC 容器中@Beanpublic EncryptorManager encryptorManager(MybatisPlusProperties mybatisPlusProperties) {return new EncryptorManager(mybatisPlusProperties.getTypeAliasesPackage());}// 加密拦截器@Beanpublic MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {return new MybatisEncryptInterceptor(encryptorManager, properties);}// 解密拦截器@Beanpublic MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {return new MybatisDecryptInterceptor(encryptorManager, properties);}}
package org.dromara.common.encrypt.properties;import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;/*** 加解密属性配置类** @author 老马* @version 4.6.0*/
@Data
@ConfigurationProperties(prefix = "mybatis-encryptor") // 根据前缀匹配
public class EncryptorProperties {/*** 过滤开关*/private Boolean enable;/*** 默认算法*/private AlgorithmType algorithm;/*** 安全秘钥*/private String password;/*** 公钥*/private String publicKey;/*** 私钥*/private String privateKey;/*** 编码方式,base64/hex*/private EncodeType encode;}
package org.dromara.common.encrypt.interceptor;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.dromara.common.encrypt.core.EncryptContext;
import org.dromara.common.encrypt.core.EncryptorManager;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import org.dromara.common.encrypt.properties.EncryptorProperties;import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.*;/*** 出参解密拦截器** @author 老马* @version 4.6.0*/
@Slf4j
@Intercepts({@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
}) // 这个意思是拦截 目标为:ResultSetHandler.class, 拦截结果集// 方法名为:handleResultSets,// 参数为:Statement.class
@AllArgsConstructor
public class MybatisDecryptInterceptor implements Interceptor {private final EncryptorManager encryptorManager;private final EncryptorProperties defaultProperties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 拦截到结果集Object result = invocation.proceed();if (result == null) {return null;}// 如果结果集不空,就将结果集解密后返回。decryptHandler(result);return result;}/*** 解密对象** @param sourceObject 待加密对象*/private void decryptHandler(Object sourceObject) {if (ObjectUtil.isNull(sourceObject)) {return;}if (sourceObject instanceof Map<?, ?> map) {new HashSet<>(map.values()).forEach(this::decryptHandler);return;}if (sourceObject instanceof List<?> list) {if(CollUtil.isEmpty(list)) {return;}// 判断第一个元素是否含有注解。如果没有直接返回,提高效率Object firstItem = list.get(0);if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {return;}list.forEach(this::decryptHandler);return;}// 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());if(ObjectUtil.isNull(fields)){return;}try {for (Field field : fields) {field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));}} catch (Exception e) {log.error("处理解密字段时出错", e);}}/*** 字段值进行加密。通过字段的批注注册新的加密算法** @param value 待加密的值* @param field 待加密字段* @return 加密后结果*/private String decryptField(String value, Field field) {if (ObjectUtil.isNull(value)) {return null;}EncryptField encryptField = field.getAnnotation(EncryptField.class);EncryptContext encryptContext = new EncryptContext();encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());return this.encryptorManager.decrypt(value, encryptContext);}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
package org.dromara.common.encrypt.interceptor;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.dromara.common.encrypt.core.EncryptContext;
import org.dromara.common.encrypt.core.EncryptorManager;
import org.dromara.common.encrypt.enumd.AlgorithmType;
import org.dromara.common.encrypt.enumd.EncodeType;
import org.dromara.common.encrypt.properties.EncryptorProperties;
import org.springframework.web.servlet.HandlerInterceptor;import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.*;/*** 入参加密拦截器** @author 老马* @version 4.6.0*/
@Slf4j
@Intercepts({@Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class})
}) // 这个意思是拦截 目标为:ParameterHandler.class, 拦截参数处理// 方法名为:setParameters,// 参数为:PreparedStatement.class
@AllArgsConstructor
public class MybatisEncryptInterceptor implements Interceptor { // 继承Interceptor接口,插件需要实现这个接口// 而我们平常写的拦截器是继承了 HandlerInterceptor 接口// 实现 Mybatis 自定义插件private final EncryptorManager encryptorManager;private final EncryptorProperties defaultProperties;@Override// 这个是核心方法,用于实现拦截逻辑public Object intercept(Invocation invocation) throws Throwable {return invocation;}@Override// 这个方法用于对目标进行包装,返回一个代理对象public Object plugin(Object target) {// 拦截器只对ParameterHandler进行加密if (target instanceof ParameterHandler parameterHandler) {// 获得参数 -- 待加密对象Object parameterObject = parameterHandler.getParameterObject();if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {// 对参数加密进行加密this.encryptHandler(parameterObject);}}return target;}/*** 加密对象** @param sourceObject 待加密对象*/private void encryptHandler(Object sourceObject) {// 如果对象为空,直接返回if (ObjectUtil.isNull(sourceObject)) {return;}// 判断是否为map,如果是,就递归加密if (sourceObject instanceof Map<?, ?> map) {new HashSet<>(map.values()).forEach(this::encryptHandler);return;}// 判断是否为list,如果是,就递归加密if (sourceObject instanceof List<?> list) {if(CollUtil.isEmpty(list)) {return;}// 判断第一个元素是否含有注解。如果没有直接返回,提高效率// List中放的是对象, 它们的字段都相同, 所以只需要判断第一个元素即可Object firstItem = list.get(0);if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {return;}list.forEach(this::encryptHandler);return;}// 不在缓存中的类,就是没有加密注解的类(当然也有可能是typeAliasesPackage写错)Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());if(ObjectUtil.isNull(fields)){return;}try {// 对加密字段进行加密for (Field field : fields) {field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));}} catch (Exception e) {log.error("处理加密字段时出错", e);}}/*** 字段值进行加密。通过字段的批注注册新的加密算法** @param value 待加密的值* @param field 待加密字段* @return 加密后结果*/private String encryptField(String value, Field field) {if (ObjectUtil.isNull(value)) {return null;}EncryptField encryptField = field.getAnnotation(EncryptField.class);// 设置加密上下文EncryptContext encryptContext = new EncryptContext();encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());// 进行加密return this.encryptorManager.encrypt(value, encryptContext);}@Overridepublic void setProperties(Properties properties) {}
}
package org.dromara.common.encrypt.core;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.EncryptField;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.util.ClassUtils;import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;/*** 加密管理类** @author 老马* @version 4.6.0*/
@Slf4j
@NoArgsConstructor
public class EncryptorManager {/*** 缓存加密器*/// key:加密配置,value:加密执行者Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();/*** 类加密字段缓存*/// key:实体类,value:加密字段集合Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();/*** 构造方法传入类加密字段缓存** @param typeAliasesPackage 实体类包*/public EncryptorManager(String typeAliasesPackage) {// 当前项目启动,扫描实体类包,获取加密字段scanEncryptClasses(typeAliasesPackage);}/*** 获取类加密字段缓存*/public Set<Field> getFieldCache(Class<?> sourceClazz) {if (ObjectUtil.isNotNull(fieldCache)) {return fieldCache.get(sourceClazz);}return null;}/*** 注册加密执行者到缓存** @param encryptContext 加密执行者需要的相关配置参数*/public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {if (encryptorMap.containsKey(encryptContext)) {return encryptorMap.get(encryptContext);}// 根据算法获取加密执行者IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);encryptorMap.put(encryptContext, encryptor);return encryptor;}/*** 移除缓存中的加密执行者** @param encryptContext 加密执行者需要的相关配置参数*/public void removeEncryptor(EncryptContext encryptContext) {this.encryptorMap.remove(encryptContext);}/*** 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。** @param value          待加密的值* @param encryptContext 加密相关的配置信息*/public String encrypt(String value, EncryptContext encryptContext) {IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);return encryptor.encrypt(value, encryptContext.getEncode());}/*** 根据配置进行解密** @param value          待解密的值* @param encryptContext 加密相关的配置信息*/public String decrypt(String value, EncryptContext encryptContext) {IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);return encryptor.decrypt(value);}/*** 通过 typeAliasesPackage 设置的扫描包 扫描缓存实体*/private void scanEncryptClasses(String typeAliasesPackage) {PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();// 获取所有扫描包的路径String[] packagePatternArray = StringUtils.splitPreserveAllTokens(typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;try {// 对每个路径下的包进行扫描for (String packagePattern : packagePatternArray) {String path = ClassUtils.convertClassNameToResourcePath(packagePattern);Resource[] resources = resolver.getResources(classpath + path + "/*.class");for (Resource resource : resources) {// 获取类元数据,获取类名,如果有加密注解,则加入缓存ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();Class<?> clazz = Resources.classForName(classMetadata.getClassName());// 封装方法,获取加密字段集合Set<Field> encryptFieldSet = getEncryptFieldSetFromClazz(clazz);if (CollUtil.isNotEmpty(encryptFieldSet)) {fieldCache.put(clazz, encryptFieldSet);}}}} catch (Exception e) {log.error("初始化数据安全缓存时出错:{}", e.getMessage());}}/*** 获得一个类的加密字段集合*/private Set<Field> getEncryptFieldSetFromClazz(Class<?> clazz) {Set<Field> fieldSet = new HashSet<>();// 判断clazz如果是接口,内部类,匿名类就直接返回if (clazz.isInterface() || clazz.isMemberClass() || clazz.isAnonymousClass()) {return fieldSet;}while (clazz != null) {Field[] fields = clazz.getDeclaredFields();fieldSet.addAll(Arrays.asList(fields));clazz = clazz.getSuperclass();}fieldSet = fieldSet.stream().filter(field ->field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class).collect(Collectors.toSet());for (Field field : fieldSet) {field.setAccessible(true);}return fieldSet;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1541614.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

自定义dialog 背景属性差异

比如正常要实现的dialog效果&#xff1a; 代码如此&#xff1a; public class SimpleDialog extends Dialog {private Button permissionokTv;//确定按钮private Button permissionnoTv;//取消按钮private TextView permissiontitleTv;//消息标题文本private TextView permiss…

[OpenGL]使用OpenGL绘制带纹理三角形

一、简介 本文介绍了如何使用使用OpenGL绘制带纹理三角形。 在绘制带纹理的三角形时&#xff0c; 首先使用.h读取准备好的.png格式的图片作为纹理&#xff0c;然后在fragment shader中使用 ... in vec2 textureCoord; uniform sampler2D aTexture1; void main() {FragColor …

Elionix 电子束曝光系统

Elionix 电子束曝光系统 - 上海纳腾仪器有限公司 -

您可能一直在寻找的 10 个非常有用的前端库

文章目录 前言正文1.radash2.dayjs3.driver4.formkit/drag-and-drop5.logicflow6.ProgressBar7.tesseract8.zxcvbn9.sunshine-track10.lottie 前言 前端开发中&#xff0c;总有一些重复性的工作让我们疲于奔命。为了提高开发效率&#xff0c;我们精心挑选了10个功能强大、易于…

数据结构与算法——Java实现 7.习题——反转链表

当你穿过了暴风雨&#xff0c;你已不是原来那个人 —— 24.9.21 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输…

【Stm32】从零建立一个工程

这里我们创建“STM32F103”系列的文件&#xff0c;基于“固件库” 1.固件库获取 https://www.st.com.cn/zh/embedded-software/stm32-standard-peripheral-libraries.html 2.使用Keil创建.uvprojx文件 前提是已经下载好了“芯片对应的固件” 3.复制底层驱动代码 将固件库下的…

大数据新视界 --大数据大厂之JavaScript在大数据前端展示中的精彩应用

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

linux安装Anaconda3

先将Anaconda3安装包下载好&#xff0c;然后在主文件夹里新建一个文件夹&#xff0c;将Anaconda3安装包拖进去。 打开终端未来不出现缺东西的异常情况&#xff0c;我们先安装 yum install -bzip2然后进入根目录下&#xff0c;在进入Anaconda3文件夹下 sh包安装方式 sh Anac…

【二十四】【QT开发应用】ScorllArea应用3,补全ScorllArea代码以及ListWidget与ScorllArea联动的信号槽和槽函数编写

补全ScorllArea代码逻辑 我们将其他ListItem项目全部设置成和基本设置一样的代码&#xff0c;唯独不一样的就是把题头的label修改成对应的文本&#xff0c;例如基本设置&#xff0c;云盘设置等。 Widget对应一个类 每一个Widget创建对应的类&#xff0c;头文件和cpp文件&am…

为什么大多数的程序员的编程界面背景都是黑色的?

不光编程IDE软件界面是黑色&#xff0c;市场上很多软件也是黑色或灰色背景为主&#xff0c;比如PS、Pr、AutoCAD等。很多商业PPT、设计广告是黑色背景&#xff0c;这几年不少汽车品牌logo也改成单黑色。 看来黑色不光是程序员的偏爱&#xff0c;也是符合大多数人需求的颜色。 …

数字基带之相移键控PSK

1 相移键控定义 相移键控是指用载波的相移位变化来传递信号&#xff0c;不改变载波的幅度和频率&#xff0c;可用下面的公式表示。 是载波的幅度&#xff0c;是载波的角频率&#xff0c;是载波的瞬时相位&#xff0c;是载波的初始相位。如果需要调制的信号为1bit的二进制数&am…

链表(单向不带头非循环)

声明 链表题考的都是单向不带头非循环&#xff0c;所以在本专栏中只介绍这一种结构&#xff0c;实际中链表的结构非常多样&#xff0c;组合起来就有8种链表结构。 链表的实现 创建一个链表 注意&#xff1a;此处简单粗暴创建的链表只是为了初学者好上手。 public class MyS…

Spring(三)Spring事件+计划任务+条件注解+SpringAware

Application Event 事件 当一个Bean处理完一个任务之后&#xff0c;希望另一个Bean知道并做出相应的处理&#xff0c;这时需要让另外一个Bean监听当前Bean所发送的事件 自定义事件&#xff0c;集成ApplicationEvent自定义事件监听器&#xff0c;实现ApplicationListener使用容…

S-Clustr-Simple 飞机大战:骇入现实的建筑灯光游戏

项目地址:https://github.com/MartinxMax/S-Clustr/releases Video https://www.youtube.com/watch?vr3JIZY1olro 飞机大战 这是一个影子集群的游戏插件&#xff0c;可以将游戏画面映射到现实的设备&#xff0c;允许恶意控制来完成游戏。亦或者设备部署在某建筑物中,来控制…

电脑硬件-机械硬盘

简介 机械硬盘是电脑的主要存储媒介之一&#xff0c;通常用于存储一些文件资料或者学习视频笔记等比较大的内容。 结构 采用磁盘存储数据&#xff0c;使用温彻斯特的结构&#xff0c;特有四个特点&#xff1a; 1.磁头、盘片和运动机构安装在一个密封的腔体内。 2.盘片告诉旋…

AI大模型算法工程师经典面试题————为什么 Bert 的三个 Embedding 可以进行相加?

大模型算法工程师经典面试题————为什么 Bert 的三个 Embedding 可以进行相加&#xff1f; 为什么 Bert 的三个 Embedding 可以进行相加&#xff1f; Token Embedding、Segment Embedding、Position Embedding的意义我已经清楚了&#xff0c;但是这三个向量为什么可以相加…

数据中台系统产品原型RP原型Axure高保真交互原型 源文件分享

在数字化时代&#xff0c;数据已经成为企业最宝贵的资产之一。为了更好地管理和利用这些数据&#xff0c;这边为大家整理了一套数据中台Axure高保真原型。这套原型致力于为企业提供全方位的数据服务&#xff0c;助力企业实现数据驱动的创新发展。 下载及预览地址&#xff1a;h…

MATLAB智能优化算法-学习笔记(3)——大规模邻域搜索算法求解旅行商问题【过程+代码】

一、问题描述 旅行商问题(TSP, Traveling Salesman Problem)是组合优化中的经典问题之一。给定一组城市和每对城市之间的距离,要求找到一条最短的路径,使旅行商从某个城市出发,访问每个城市一次并最终回到出发点。TSP问题广泛应用于物流配送、工厂调度、芯片制造等领域。…

1、等保测评介绍

数据来源&#xff1a;等保测评基础知识学习(1.02.0)2024最新版_哔哩哔哩_bilibili 等级保护的定义&#xff1a; 对国家秘密信息、法人或其他组织及公民专有信息以及公开信息&#xff0c;按照其重要程度对信息系统实施分等级安全保护。这包括对使用的安全产品进行等级管理&…

基于协同过滤算法的商品推荐系统

系统展示 用户前台界面 管理员后台界面 商家后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;用户每天面临的信息量呈爆炸式增长&#xff0c;如何有效地筛选出用户感兴趣的内容成为一大挑战。在此背景下&#xff0c;基于协同过滤算法的商品推荐系统应运而生。该系统通过…