refactor(encrypt): 拆分字段加密、API 加密模块

This commit is contained in:
2025-08-20 21:44:40 +08:00
parent e5002b8bfc
commit e9bf92ea1f
46 changed files with 525 additions and 343 deletions

View File

@@ -160,8 +160,11 @@ continew-starter
│ ├─ continew-starter-data-core核心模块 │ ├─ continew-starter-data-core核心模块
│ ├─ continew-starter-data-mpMyBatis Plus │ ├─ continew-starter-data-mpMyBatis Plus
│ └─ continew-starter-data-mfMyBatis Flex │ └─ continew-starter-data-mfMyBatis Flex
├─ continew-starter-encrypt加密模块
│ ├─ continew-starter-encrypt-core核心模块
│ ├─ continew-starter-encrypt-field字段加密
│ └─ continew-starter-encrypt-apiAPI 加密)
├─ continew-starter-security安全模块 ├─ continew-starter-security安全模块
│ ├─ continew-starter-security-crypto加密字段加解密
│ ├─ continew-starter-security-mask脱敏JSON 数据脱敏) │ ├─ continew-starter-security-mask脱敏JSON 数据脱敏)
│ ├─ continew-starter-security-xssXSS 过滤) │ ├─ continew-starter-security-xssXSS 过滤)
│ └─ continew-starter-security-sensitivewords敏感词 │ └─ continew-starter-security-sensitivewords敏感词

View File

@@ -104,12 +104,25 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 安全模块 - 加密 --> <!-- 加密模块 - 字段加密 -->
<dependency> <dependency>
<groupId>top.continew.starter</groupId> <groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security-crypto</artifactId> <artifactId>continew-starter-encrypt-field</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 加密模块 - API 加密 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 加密模块 - 核心模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 脱敏 --> <!-- 安全模块 - 脱敏 -->
<dependency> <dependency>
<groupId>top.continew.starter</groupId> <groupId>top.continew.starter</groupId>

View File

@@ -32,9 +32,9 @@ public class OrderedConstants {
public static final class Filter { public static final class Filter {
/** /**
* API加/密过滤器顺序 * API 加密过滤器顺序
*/ */
public static final int API_CRYPTO_FILTER = Ordered.HIGHEST_PRECEDENCE; public static final int API_ENCRYPT_FILTER = Ordered.HIGHEST_PRECEDENCE;
/** /**
* 链路追踪过滤器顺序 * 链路追踪过滤器顺序

View File

@@ -49,31 +49,41 @@ public class PropertiesConstants {
*/ */
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response"; public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
/**
* 加密配置
*/
public static final String ENCRYPT = CONTINEW_STARTER + StringConstants.DOT + "encrypt";
/**
* 加密-密码编码器
*/
public static final String ENCRYPT_PASSWORD_ENCODER = ENCRYPT + StringConstants.DOT + "password-encoder";
/**
* 加密-字段加密
*/
public static final String ENCRYPT_FIELD = ENCRYPT + StringConstants.DOT + "field";
/**
* 加密-API 加密
*/
public static final String ENCRYPT_API = ENCRYPT + StringConstants.DOT + "api";
/** /**
* 安全配置 * 安全配置
*/ */
public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security"; public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security";
/** /**
* 安全-数据加/解密配置 * 安全-XSS 配置
*/ */
public static final String SECURITY_CRYPTO = SECURITY + StringConstants.DOT + "crypto"; public static final String SECURITY_XSS = SECURITY + StringConstants.DOT + "xss";
/**
* 安全-API加/解密配置
*/
public static final String SECURITY_API_CRYPTO = SECURITY + StringConstants.DOT + "api-crypto";
/** /**
* 安全-敏感词配置 * 安全-敏感词配置
*/ */
public static final String SECURITY_SENSITIVE_WORDS = SECURITY + StringConstants.DOT + "sensitive-words"; public static final String SECURITY_SENSITIVE_WORDS = SECURITY + StringConstants.DOT + "sensitive-words";
/**
* 安全-XSS 配置
*/
public static final String SECURITY_XSS = SECURITY + StringConstants.DOT + "xss";
/** /**
* 限流配置 * 限流配置
*/ */

View File

@@ -20,12 +20,16 @@ import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.http.server.PathContainer; import org.springframework.http.server.PathContainer;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser; import org.springframework.web.util.pattern.PathPatternParser;
@@ -135,4 +139,31 @@ public class SpringWebUtils {
.getUrlMap(); .getUrlMap();
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap); ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
} }
/**
* 获取处理器方法
*
* @param request 请求
* @return 处理器方法
* @since 2.14.0
*/
public static HandlerMethod getHandlerMethod(HttpServletRequest request) {
try {
RequestMappingHandlerMapping handlerMapping = SpringUtil
.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
// 检查是否存在处理链
if (handlerExecutionChain == null) {
return null;
}
// 获取处理器
Object handler = handlerExecutionChain.getHandler();
if (handler instanceof HandlerMethod handlerMethod) {
return handlerMethod;
}
return null;
} catch (Exception e) {
return null;
}
}
} }

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-encrypt-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 加密模块 - API 加密</description>
<dependencies>
<!-- 加密模块 - 核心模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.annotation; package top.continew.starter.encrypt.api.annotation;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* API加密注解 * API 加密注解
* *
* @author lishuyan * @author lishuyan
* @since 2.14.0 * @since 2.14.0
@@ -30,8 +30,7 @@ import java.lang.annotation.*;
public @interface ApiEncrypt { public @interface ApiEncrypt {
/** /**
* 默认API响应加密 * 是否加密响应
*/ */
boolean response() default true; boolean response() default true;
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.api.autoconfigure;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.servlet.DispatcherType; import jakarta.servlet.DispatcherType;
@@ -27,40 +27,38 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import top.continew.starter.core.constant.OrderedConstants; import top.continew.starter.core.constant.OrderedConstants;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.crypto.filter.ApiCryptoFilter; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.encrypt.api.filter.ApiEncryptFilter;
/** /**
* API/密自动配置 * API 密自动配置
* *
* @author lishuyan * @author lishuyan
* @author Charles7c
* @since 2.14.0 * @since 2.14.0
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(ApiCryptoProperties.class) @EnableConfigurationProperties(ApiEncryptProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_API_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(prefix = PropertiesConstants.ENCRYPT_API, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class ApiCryptoAutoConfiguration { public class ApiEncryptAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ApiCryptoAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(ApiEncryptAutoConfiguration.class);
/** /**
* API/密过滤器 * API 密过滤器
*
* @param properties 配置
* @return 过滤器
*/ */
@Bean @Bean
public FilterRegistrationBean<ApiCryptoFilter> apiCryptoFilterRegistration(ApiCryptoProperties properties) { public FilterRegistrationBean<ApiEncryptFilter> apiEncryptFilter(ApiEncryptProperties properties) {
FilterRegistrationBean<ApiCryptoFilter> registration = new FilterRegistrationBean<>(); FilterRegistrationBean<ApiEncryptFilter> registrationBean = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST); registrationBean.setFilter(new ApiEncryptFilter(properties));
registration.setFilter(new ApiCryptoFilter(properties)); registrationBean.setOrder(OrderedConstants.Filter.API_ENCRYPT_FILTER);
registration.addUrlPatterns("/*"); registrationBean.addUrlPatterns(StringConstants.PATH_PATTERN_CURRENT_DIR);
registration.setName("apiCryptoFilter"); registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
registration.setOrder(OrderedConstants.Filter.API_CRYPTO_FILTER); return registrationBean;
return registration;
} }
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Security-API-Crypto' completed initialization."); log.debug("[ContiNew Starter] - Auto Configuration 'Encrypt-API' completed initialization.");
} }
} }

View File

@@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.api.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
/** /**
* API/解密属性配置 * API 加密配置属性
* *
* @author lishuyan * @author lishuyan
* @since 2.14.0 * @since 2.14.0
*/ */
@ConfigurationProperties(PropertiesConstants.SECURITY_API_CRYPTO) @ConfigurationProperties(PropertiesConstants.ENCRYPT_API)
public class ApiCryptoProperties { public class ApiEncryptProperties {
/** /**
* 是否启用 * 是否启用
@@ -36,7 +36,7 @@ public class ApiCryptoProperties {
/** /**
* 请求头中 AES 密钥 键名 * 请求头中 AES 密钥 键名
*/ */
private String secretKeyHeader = "X-Api-Crypto"; private String secretKeyHeader = "X-Api-Encrypt";
/** /**
* 响应加密公钥 * 响应加密公钥

View File

@@ -14,37 +14,33 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.filter; package top.continew.starter.encrypt.api.filter;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.*; import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod; import top.continew.starter.core.util.SpringWebUtils;
import org.springframework.web.servlet.HandlerExecutionChain; import top.continew.starter.encrypt.api.annotation.ApiEncrypt;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import top.continew.starter.encrypt.api.autoconfigure.ApiEncryptProperties;
import top.continew.starter.security.crypto.annotation.ApiEncrypt;
import top.continew.starter.security.crypto.autoconfigure.ApiCryptoProperties;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
/** /**
* API/密过滤器 * API 密过滤器
* *
* @author lishuyan * @author lishuyan
* @author Charles7c
* @since 2.14.0 * @since 2.14.0
*/ */
public class ApiCryptoFilter implements Filter { public class ApiEncryptFilter implements Filter {
/** private final ApiEncryptProperties properties;
* API加/密配置
*/
private final ApiCryptoProperties properties;
public ApiCryptoFilter(ApiCryptoProperties properties) { public ApiEncryptFilter(ApiEncryptProperties properties) {
this.properties = properties; this.properties = properties;
} }
@@ -54,10 +50,8 @@ public class ApiCryptoFilter implements Filter {
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse; HttpServletResponse response = (HttpServletResponse)servletResponse;
// 获取API加密注解 // 是否加密响应
ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(request); boolean isResponseEncrypt = this.isResponseEncrypt(request);
// 响应加密标识
boolean responseEncryptFlag = ObjectUtil.isNotNull(apiEncrypt) && apiEncrypt.response();
// 密钥标头 // 密钥标头
String secretKeyHeader = properties.getSecretKeyHeader(); String secretKeyHeader = properties.getSecretKeyHeader();
ServletRequest requestWrapper = null; ServletRequest requestWrapper = null;
@@ -73,7 +67,7 @@ public class ApiCryptoFilter implements Filter {
} }
} }
// 响应加密响应包装器替换响应体加密包装器 // 响应加密响应包装器替换响应体加密包装器
if (responseEncryptFlag) { if (isResponseEncrypt) {
responseBodyEncryptWrapper = new ResponseBodyEncryptWrapper(response); responseBodyEncryptWrapper = new ResponseBodyEncryptWrapper(response);
responseWrapper = responseBodyEncryptWrapper; responseWrapper = responseBodyEncryptWrapper;
} }
@@ -81,7 +75,7 @@ public class ApiCryptoFilter implements Filter {
chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), ObjectUtil chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), ObjectUtil
.defaultIfNull(responseWrapper, response)); .defaultIfNull(responseWrapper, response));
// 响应加密执行完成后响应密文 // 响应加密执行完成后响应密文
if (responseEncryptFlag) { if (isResponseEncrypt) {
servletResponse.reset(); servletResponse.reset();
// 获取密文 // 获取密文
String encryptContent = responseBodyEncryptWrapper.getEncryptContent(response, properties String encryptContent = responseBodyEncryptWrapper.getEncryptContent(response, properties
@@ -92,30 +86,16 @@ public class ApiCryptoFilter implements Filter {
} }
/** /**
* 获取 ApiEncrypt 注解 * 是否加密响应
* *
* @param request HTTP请求 * @param request 请求对象
* @return ApiEncrypt注解如果未找到则返回null * @return 是否加密响应
*/ */
private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest request) { private boolean isResponseEncrypt(HttpServletRequest request) {
try { // 获取 API 加密注解
RequestMappingHandlerMapping handlerMapping = SpringUtil ApiEncrypt apiEncrypt = Optional.ofNullable(SpringWebUtils.getHandlerMethod(request))
.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); .map(h -> h.getMethodAnnotation(ApiEncrypt.class))
HandlerExecutionChain mappingHandler = handlerMapping.getHandler(request); .orElse(null);
// 检查是否存在处理链 return apiEncrypt != null && apiEncrypt.response();
if (ObjectUtil.isNull(mappingHandler)) {
return null;
}
// 获取处理器
Object handler = mappingHandler.getHandler();
// 检查是否为HandlerMethod类型
if (!(handler instanceof HandlerMethod handlerMethod)) {
return null;
}
// 获取方法上的ApiEncrypt注解
return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
} catch (Exception e) {
return null;
}
} }
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.filter; package top.continew.starter.encrypt.api.filter;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
@@ -23,7 +23,7 @@ import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import top.continew.starter.security.crypto.util.EncryptHelper; import top.continew.starter.encrypt.util.EncryptUtils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -63,14 +63,14 @@ public class RequestBodyDecryptWrapper extends HttpServletRequestWrapper {
// 通过 请求头 获取 AES 密钥密钥内容经过 RSA 加密 // 通过 请求头 获取 AES 密钥密钥内容经过 RSA 加密
String secretKeyByRsa = request.getHeader(secretKeyHeader); String secretKeyByRsa = request.getHeader(secretKeyHeader);
// 通过 RSA 解密获取 AES 密钥密钥内容经过 Base64 编码 // 通过 RSA 解密获取 AES 密钥密钥内容经过 Base64 编码
String secretKeyByBase64 = EncryptHelper.decryptByRsa(secretKeyByRsa, privateKey); String secretKeyByBase64 = EncryptUtils.decryptByRsa(secretKeyByRsa, privateKey);
// 通过 Base64 解码获取 AES 密钥 // 通过 Base64 解码获取 AES 密钥
String aesSecretKey = EncryptHelper.decodeByBase64(secretKeyByBase64); String aesSecretKey = EncryptUtils.decodeByBase64(secretKeyByBase64);
request.setCharacterEncoding(CharsetUtil.UTF_8); request.setCharacterEncoding(CharsetUtil.UTF_8);
byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false); byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
String requestBody = new String(readBytes, StandardCharsets.UTF_8); String requestBody = new String(readBytes, StandardCharsets.UTF_8);
// 通过 AES 密钥解密 请求体 // 通过 AES 密钥解密 请求体
return EncryptHelper.decryptByAes(requestBody, aesSecretKey).getBytes(StandardCharsets.UTF_8); return EncryptUtils.decryptByAes(requestBody, aesSecretKey).getBytes(StandardCharsets.UTF_8);
} }
@Override @Override

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.filter; package top.continew.starter.encrypt.api.filter;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
@@ -24,7 +24,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper; import jakarta.servlet.http.HttpServletResponseWrapper;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.security.crypto.util.EncryptHelper; import top.continew.starter.encrypt.util.EncryptUtils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -94,9 +94,9 @@ public class ResponseBodyEncryptWrapper extends HttpServletResponseWrapper {
// 生成 AES 密钥 // 生成 AES 密钥
String aesSecretKey = RandomUtil.randomString(32); String aesSecretKey = RandomUtil.randomString(32);
// Base64 编码 // Base64 编码
String secretKeyByBase64 = EncryptHelper.encodeByBase64(aesSecretKey); String secretKeyByBase64 = EncryptUtils.encodeByBase64(aesSecretKey);
// RSA 加密 // RSA 加密
String secretKeyByRsa = EncryptHelper.encryptByRsa(secretKeyByBase64, publicKey); String secretKeyByRsa = EncryptUtils.encryptByRsa(secretKeyByBase64, publicKey);
// 设置响应头 // 设置响应头
response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, secretKeyHeader); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, secretKeyHeader);
response.setHeader(secretKeyHeader, secretKeyByRsa); response.setHeader(secretKeyHeader, secretKeyByRsa);
@@ -104,7 +104,7 @@ public class ResponseBodyEncryptWrapper extends HttpServletResponseWrapper {
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, StringConstants.ASTERISK); response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, StringConstants.ASTERISK);
response.setCharacterEncoding(CharsetUtil.UTF_8); response.setCharacterEncoding(CharsetUtil.UTF_8);
// 通过 AES 密钥对原始内容进行加密 // 通过 AES 密钥对原始内容进行加密
return EncryptHelper.encryptByAes(this.getContent(), aesSecretKey); return EncryptUtils.encryptByAes(this.getContent(), aesSecretKey);
} }
@Override @Override

View File

@@ -0,0 +1 @@
top.continew.starter.encrypt.api.autoconfigure.ApiEncryptAutoConfiguration

View File

@@ -5,17 +5,23 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>top.continew.starter</groupId> <groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security</artifactId> <artifactId>continew-starter-encrypt</artifactId>
<version>${revision}</version> <version>${revision}</version>
</parent> </parent>
<artifactId>continew-starter-security-crypto</artifactId> <artifactId>continew-starter-encrypt-core</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>ContiNew Starter 安全模块 - 加密</description> <description>ContiNew Starter 加密模块 - 核心模块</description>
<dependencies> <dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-core</artifactId>
</dependency>
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) --> <!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
@@ -27,11 +33,5 @@
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId> <artifactId>spring-security-crypto</artifactId>
</dependency> </dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.autoconfigure;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -24,59 +24,29 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.core.util.validation.CheckUtils; import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm; import top.continew.starter.encrypt.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.crypto.mybatis.MyBatisDecryptInterceptor; import top.continew.starter.encrypt.util.PasswordEncoderUtil;
import top.continew.starter.security.crypto.mybatis.MyBatisEncryptInterceptor;
import top.continew.starter.security.crypto.util.EncryptHelper;
import top.continew.starter.security.crypto.util.PasswordEncoderUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* /密自动配置 * 码编码器自动配置
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @since 2.14.0
* @since 1.4.0
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(CryptoProperties.class) @EnableConfigurationProperties(PasswordEncoderProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(prefix = PropertiesConstants.ENCRYPT_PASSWORD_ENCODER, name = PropertiesConstants.ENABLED, havingValue = "true")
@PropertySource(value = "classpath:default-crypto.yml", factory = GeneralPropertySourceFactory.class) public class PasswordEncoderAutoConfiguration {
public class CryptoAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class); private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class);
private final CryptoProperties properties;
public CryptoAutoConfiguration(CryptoProperties properties) {
this.properties = properties;
}
/**
* MyBatis 加密拦截器配置
*/
@Bean
@ConditionalOnMissingBean
public MyBatisEncryptInterceptor mybatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor();
}
/**
* MyBatis 解密拦截器配置
*/
@Bean
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
public MyBatisDecryptInterceptor mybatisDecryptInterceptor() {
return new MyBatisDecryptInterceptor();
}
/** /**
* 密码编码器配置 * 密码编码器配置
@@ -86,9 +56,7 @@ public class CryptoAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO + ".password-encoder", name = PropertiesConstants.ENABLED, havingValue = "true") public PasswordEncoder passwordEncoder(PasswordEncoderProperties properties) {
public PasswordEncoder passwordEncoder() {
PasswordEncoderProperties passwordEncoderProperties = properties.getPasswordEncoder();
Map<String, PasswordEncoder> encoders = new HashMap<>(); Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.BCRYPT)); .getEncoder(PasswordEncoderAlgorithm.BCRYPT));
@@ -98,14 +66,13 @@ public class CryptoAutoConfiguration {
.getEncoder(PasswordEncoderAlgorithm.PBKDF2)); .getEncoder(PasswordEncoderAlgorithm.PBKDF2));
encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.ARGON2)); .getEncoder(PasswordEncoderAlgorithm.ARGON2));
PasswordEncoderAlgorithm algorithm = passwordEncoderProperties.getAlgorithm(); PasswordEncoderAlgorithm algorithm = properties.getAlgorithm();
CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm); CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm);
return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders); return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders);
} }
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
EncryptHelper.init(properties); log.debug("[ContiNew Starter] - Auto Configuration 'Encrypt-Password Encoder' completed initialization.");
log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization.");
} }
} }

View File

@@ -14,14 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.autoconfigure;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm; import top.continew.starter.encrypt.enums.PasswordEncoderAlgorithm;
/** /**
* 密码编码器配置属性 * 密码编码器配置属性
* *
* @author Jasmine * @author Jasmine
* @author Charles7c
* @since 1.3.0 * @since 1.3.0
*/ */
public class PasswordEncoderProperties { public class PasswordEncoderProperties {
@@ -29,18 +30,18 @@ public class PasswordEncoderProperties {
/** /**
* 是否启用 * 是否启用
*/ */
private boolean enabled = true; private Boolean enabled;
/** /**
* 默认启用的编码器算法默认BCrypt 加密算法 * 默认启用的编码器算法默认BCrypt 加密算法
*/ */
private PasswordEncoderAlgorithm algorithm = PasswordEncoderAlgorithm.BCRYPT; private PasswordEncoderAlgorithm algorithm = PasswordEncoderAlgorithm.BCRYPT;
public boolean isEnabled() { public Boolean getEnabled() {
return enabled; return enabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(Boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }

View File

@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.context;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.encrypt.encryptor.IEncryptor;
import top.continew.starter.security.crypto.enums.Algorithm; import top.continew.starter.encrypt.enums.Algorithm;
import java.util.Objects; import java.util.Objects;

View File

@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* 加密器基类 * 加密器基类

View File

@@ -14,20 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto; import cn.hutool.crypto.symmetric.SymmetricCrypto;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* 对称加/解密处理 * 对称加
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @author lishuyan

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* AESAdvanced Encryption Standard /解密处理 * AESAdvanced Encryption Standard
* <p> * <p>
* 美国国家标准与技术研究院(NIST)采纳的对称加密算法标准提供128位192位和256位三种密钥长度以高效和安全性著称 * 美国国家标准与技术研究院(NIST)采纳的对称加密算法标准提供128位192位和256位三种密钥长度以高效和安全性著称
* </p> * </p>

View File

@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
/** /**
* Base64 /解密处理 * Base64
* <p> * <p>
* 一种用于编码二进制数据到文本格式的算法常用于邮件附件网页传输等场合但它不是一种加密算法只提供数据的编码和解码不保证数据的安全性 * 一种用于编码二进制数据到文本格式的算法常用于邮件附件网页传输等场合但它不是一种加密算法只提供数据的编码和解码不保证数据的安全性
* </p> * </p>

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* DESData Encryption Standard /解密处理 * DESData Encryption Standard
* <p> * <p>
* 一种对称加密算法使用相同的密钥进行加密和解密DES 使用 56 位密钥实际上有 64 但有 8 位用于奇偶校验和一系列置换和替换操作来加密数据 * 一种对称加密算法使用相同的密钥进行加密和解密DES 使用 56 位密钥实际上有 64 但有 8 位用于奇偶校验和一系列置换和替换操作来加密数据
* </p> * </p>

View File

@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
/** /**
* /密接口 * 加密接口
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @author lishuyan

View File

@@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.core.util.SpringUtils;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.encrypt.autoconfigure.PasswordEncoderProperties;
import top.continew.starter.security.crypto.autoconfigure.PasswordEncoderProperties; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* 密码编码器加/解密处理 * 密码编码器加
* *
* <p> * <p>
* 使用前必须注入 {@link PasswordEncoder}此加密方式不可逆适合于密码场景 * 使用前必须注入 {@link PasswordEncoder}此加密方式不可逆适合于密码场景
@@ -37,8 +37,7 @@ import top.continew.starter.security.crypto.autoconfigure.PasswordEncoderPropert
*/ */
public class PasswordEncoderEncryptor extends AbstractEncryptor { public class PasswordEncoderEncryptor extends AbstractEncryptor {
private final PasswordEncoder passwordEncoder = SpringUtil.getBean(PasswordEncoder.class); private final PasswordEncoderProperties properties = SpringUtils.getBean(PasswordEncoderProperties.class, true);
private final CryptoProperties properties = SpringUtil.getBean(CryptoProperties.class);
public PasswordEncoderEncryptor(CryptoContext context) { public PasswordEncoderEncryptor(CryptoContext context) {
super(context); super(context);
@@ -47,10 +46,10 @@ public class PasswordEncoderEncryptor extends AbstractEncryptor {
@Override @Override
public String encrypt(String plaintext) { public String encrypt(String plaintext) {
// 如果已经是加密格式直接返回 // 如果已经是加密格式直接返回
if (properties.getPasswordEncoder().getAlgorithm().getPattern().matcher(plaintext).matches()) { if (properties == null || properties.getAlgorithm().getPattern().matcher(plaintext).matches()) {
return plaintext; return plaintext;
} }
return passwordEncoder.encode(plaintext); return SpringUtil.getBean(PasswordEncoder.class).encode(plaintext);
} }
@Override @Override

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm; import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* PBEWithMD5AndDESPassword Based Encryption With MD5 And DES /解密处理 * PBEWithMD5AndDESPassword Based Encryption With MD5 And DES
* <p> * <p>
* 混合加密算法结合了 MD5 散列算法和 DESData Encryption Standard加密算法 * 混合加密算法结合了 MD5 散列算法和 DESData Encryption Standard加密算法
* </p> * </p>

View File

@@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.encryptor; package top.continew.starter.encrypt.encryptor;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.KeyType;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.context.CryptoContext;
/** /**
* RSA /解密处理 * RSA
* <p> * <p>
* 非对称加密算法由罗纳德·李维斯特Ron Rivest阿迪·沙米尔Adi Shamir和伦纳德·阿德曼Leonard Adleman于1977年提出安全性基于大数因子分解问题的困难性 * 非对称加密算法由罗纳德·李维斯特Ron Rivest阿迪·沙米尔Adi Shamir和伦纳德·阿德曼Leonard Adleman于1977年提出安全性基于大数因子分解问题的困难性
* </p> * </p>

View File

@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.enums; package top.continew.starter.encrypt.enums;
import top.continew.starter.security.crypto.encryptor.*; import top.continew.starter.encrypt.encryptor.*;
/** /**
* 加密/解密算法枚举 * 加密算法枚举
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @author lishuyan

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.enums; package top.continew.starter.encrypt.enums;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.exception; package top.continew.starter.encrypt.exception;
import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.exception.BaseException;

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.encrypt.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import java.nio.charset.StandardCharsets;
/**
* 加密工具类
*
* @author Charles7c
* @since 2.14.0
*/
public class EncryptUtils {
/**
* Base64 编码
*
* @param data 待编码数据
* @return 编码后字符串
* @author lishuyan
*/
public static String encodeByBase64(String data) {
return Base64.encode(data, StandardCharsets.UTF_8);
}
/**
* Base64 解码
*
* @param data 待解码数据
* @return 解码后字符串
* @author lishuyan
*/
public static String decodeByBase64(String data) {
return Base64.decodeStr(data, StandardCharsets.UTF_8);
}
/**
* AES 加密
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用 Base64 编码
* @author lishuyan
*/
public static String encryptByAes(String data, String password) {
if (CharSequenceUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// AES算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* AES 解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
* @author lishuyan
*/
public static String decryptByAes(String data, String password) {
if (CharSequenceUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// AES算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* RSA 公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
* @author lishuyan
*/
public static String encryptByRsa(String data, String publicKey) {
if (CharSequenceUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
}
RSA rsa = SecureUtil.rsa(null, publicKey);
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* RSA 私钥解密
*
* @param data 待解密数据
* @param privateKey 私钥
* @return 解密后字符串
* @author lishuyan
*/
public static String decryptByRsa(String data, String privateKey) {
if (CharSequenceUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("RSA需要传入私钥进行解密");
}
RSA rsa = SecureUtil.rsa(privateKey, null);
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
private EncryptUtils() {
}
}

View File

@@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.util; package top.continew.starter.encrypt.util;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm; import top.continew.starter.encrypt.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.crypto.exception.PasswordEncodeException; import top.continew.starter.encrypt.exception.PasswordEncodeException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;

View File

@@ -0,0 +1 @@
top.continew.starter.encrypt.autoconfigure.PasswordEncoderAutoConfiguration

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-encrypt-field</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 加密模块 - 字段加密</description>
<dependencies>
<!-- 加密模块 - 核心模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt-core</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.annotation; package top.continew.starter.encrypt.field.annotation;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.encrypt.encryptor.IEncryptor;
import top.continew.starter.security.crypto.enums.Algorithm; import top.continew.starter.encrypt.enums.Algorithm;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* 字段加/密注解 * 字段加密注解
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @author lishuyan
@@ -36,14 +36,14 @@ import java.lang.annotation.Target;
public @interface FieldEncrypt { public @interface FieldEncrypt {
/** /**
* 加密/解密算法 * 加密算法
*/ */
Algorithm value() default Algorithm.DEFAULT; Algorithm value() default Algorithm.DEFAULT;
/** /**
* 加密/解密处理器 * 加密处理器
* <p> * <p>
* 优先级高于加密/解密算法 * 优先级高于加密算法
* </p> * </p>
*/ */
Class<? extends IEncryptor> encryptor() default IEncryptor.class; Class<? extends IEncryptor> encryptor() default IEncryptor.class;

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.encrypt.field.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.encrypt.field.interceptor.MyBatisDecryptInterceptor;
import top.continew.starter.encrypt.field.interceptor.MyBatisEncryptInterceptor;
import top.continew.starter.encrypt.field.util.EncryptHelper;
/**
* 字段加密自动配置
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
@AutoConfiguration
@EnableConfigurationProperties(FieldEncryptProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.ENCRYPT_FIELD, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class FieldEncryptAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(FieldEncryptAutoConfiguration.class);
private final FieldEncryptProperties properties;
public FieldEncryptAutoConfiguration(FieldEncryptProperties properties) {
this.properties = properties;
}
/**
* MyBatis 加密拦截器配置
*/
@Bean
@ConditionalOnMissingBean
public MyBatisEncryptInterceptor mybatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor();
}
/**
* MyBatis 解密拦截器配置
*/
@Bean
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
public MyBatisDecryptInterceptor mybatisDecryptInterceptor() {
return new MyBatisDecryptInterceptor();
}
@PostConstruct
public void postConstruct() {
EncryptHelper.init(properties);
log.debug("[ContiNew Starter] - Auto Configuration 'Encrypt-Field' completed initialization.");
}
}

View File

@@ -14,27 +14,28 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.autoconfigure; package top.continew.starter.encrypt.field.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants; import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.crypto.enums.Algorithm; import top.continew.starter.encrypt.autoconfigure.PasswordEncoderProperties;
import top.continew.starter.encrypt.enums.Algorithm;
/** /**
* /密配置属性 * 字段加密配置属性
* *
* @author Charles7c * @author Charles7c
* @author lishuyan * @author lishuyan
* @since 1.4.0 * @since 1.4.0
*/ */
@ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO) @ConfigurationProperties(PropertiesConstants.ENCRYPT_FIELD)
public class CryptoProperties { public class FieldEncryptProperties {
/** /**
* 是否启用 * 是否启用
*/ */
private boolean enabled = true; private Boolean enabled;
/** /**
* 默认算法 * 默认算法
@@ -62,11 +63,11 @@ public class CryptoProperties {
@NestedConfigurationProperty @NestedConfigurationProperty
private PasswordEncoderProperties passwordEncoder; private PasswordEncoderProperties passwordEncoder;
public boolean isEnabled() { public Boolean getEnabled() {
return enabled; return enabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(Boolean enabled) {
this.enabled = enabled; this.enabled = enabled;
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.mybatis; package top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
@@ -22,7 +22,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException; import top.continew.starter.core.exception.BaseException;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.mybatis; package top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
@@ -26,8 +26,8 @@ import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.type.SimpleTypeRegistry; import org.apache.ibatis.type.SimpleTypeRegistry;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.util.EncryptHelper; import top.continew.starter.encrypt.field.util.EncryptHelper;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.sql.Statement; import java.sql.Statement;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.mybatis; package top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
@@ -29,8 +29,8 @@ import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.util.EncryptHelper; import top.continew.starter.encrypt.field.util.EncryptHelper;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;

View File

@@ -14,25 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package top.continew.starter.security.crypto.util; package top.continew.starter.encrypt.field.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.encrypt.context.CryptoContext;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext; import top.continew.starter.encrypt.encryptor.IEncryptor;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties; import top.continew.starter.encrypt.enums.Algorithm;
import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.enums.Algorithm; import top.continew.starter.encrypt.field.autoconfigure.FieldEncryptProperties;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -49,7 +43,7 @@ public class EncryptHelper {
/** /**
* 默认加密配置 * 默认加密配置
*/ */
private static CryptoProperties defaultProperties; private static FieldEncryptProperties defaultProperties;
/** /**
* 加密器缓存 * 加密器缓存
@@ -64,7 +58,7 @@ public class EncryptHelper {
* *
* @param properties 加密配置 * @param properties 加密配置
*/ */
public static void init(CryptoProperties properties) { public static void init(FieldEncryptProperties properties) {
defaultProperties = properties; defaultProperties = properties;
} }
@@ -105,7 +99,7 @@ public class EncryptHelper {
* @return 加密后的字符串 * @return 加密后的字符串
*/ */
public static String encrypt(String value, FieldEncrypt fieldEncrypt) { public static String encrypt(String value, FieldEncrypt fieldEncrypt) {
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) { if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null) {
return value; return value;
} }
String ciphertext = value; String ciphertext = value;
@@ -126,7 +120,7 @@ public class EncryptHelper {
* @return 加密后的字符串 * @return 加密后的字符串
*/ */
public static String encrypt(String value) { public static String encrypt(String value) {
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) { if (CharSequenceUtil.isBlank(value)) {
return value; return value;
} }
String ciphertext = value; String ciphertext = value;
@@ -148,7 +142,7 @@ public class EncryptHelper {
* @return 解密后的字符串 * @return 解密后的字符串
*/ */
public static String decrypt(String value, FieldEncrypt fieldEncrypt) { public static String decrypt(String value, FieldEncrypt fieldEncrypt) {
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) { if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null) {
return value; return value;
} }
String plaintext = value; String plaintext = value;
@@ -169,7 +163,7 @@ public class EncryptHelper {
* @return 解密后的字符串 * @return 解密后的字符串
*/ */
public static String decrypt(String value) { public static String decrypt(String value) {
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) { if (CharSequenceUtil.isBlank(value)) {
return value; return value;
} }
String plaintext = value; String plaintext = value;
@@ -223,98 +217,4 @@ public class EncryptHelper {
cryptoContext.setPublicKey(defaultProperties.getPublicKey()); cryptoContext.setPublicKey(defaultProperties.getPublicKey());
return cryptoContext; return cryptoContext;
} }
/**
* Base64编码
*
* @param data 待编码数据
* @return 编码后字符串
* @since 2.14.0
*/
public static String encodeByBase64(String data) {
return Base64.encode(data, StandardCharsets.UTF_8);
}
/**
* Base64解码
*
* @param data 待解码数据
* @return 解码后字符串
* @since 2.14.0
*/
public static String decodeByBase64(String data) {
return Base64.decodeStr(data, StandardCharsets.UTF_8);
}
/**
* AES加密
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
* @since 2.14.0
*/
public static String encryptByAes(String data, String password) {
if (CharSequenceUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// AES算法的秘钥要求是16位24位32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* AES解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
* @since 2.14.0
*/
public static String decryptByAes(String data, String password) {
if (CharSequenceUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// AES算法的秘钥要求是16位24位32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* RSA公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
* @since 2.14.0
*/
public static String encryptByRsa(String data, String publicKey) {
if (CharSequenceUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
}
RSA rsa = SecureUtil.rsa(null, publicKey);
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* RSA私钥解密
*
* @param data 待解密数据
* @param privateKey 私钥
* @return 解密后字符串
* @since 2.14.0
*/
public static String decryptByRsa(String data, String privateKey) {
if (CharSequenceUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("RSA需要传入私钥进行解密");
}
RSA rsa = SecureUtil.rsa(privateKey, null);
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
} }

View File

@@ -0,0 +1 @@
top.continew.starter.encrypt.field.autoconfigure.FieldEncryptAutoConfiguration

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-encrypt</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 加密模块</description>
<modules>
<module>continew-starter-encrypt-core</module>
<module>continew-starter-encrypt-field</module>
<module>continew-starter-encrypt-api</module>
</modules>
</project>

View File

@@ -1,2 +0,0 @@
top.continew.starter.security.crypto.autoconfigure.CryptoAutoConfiguration
top.continew.starter.security.crypto.autoconfigure.ApiCryptoAutoConfiguration

View File

@@ -1,6 +0,0 @@
--- ### 安全配置:字段加/解密配置
continew-starter.security:
crypto:
enabled: true
# 默认算法,即 @FieldEncrypt 默认采用的算法默认AES 对称加密算法)
algorithm: AES

View File

@@ -16,7 +16,6 @@
<description>ContiNew Starter 安全模块</description> <description>ContiNew Starter 安全模块</description>
<modules> <modules>
<module>continew-starter-security-crypto</module>
<module>continew-starter-security-mask</module> <module>continew-starter-security-mask</module>
<module>continew-starter-security-xss</module> <module>continew-starter-security-xss</module>
<module>continew-starter-security-sensitivewords</module> <module>continew-starter-security-sensitivewords</module>

View File

@@ -42,6 +42,7 @@
<module>continew-starter-cache</module> <module>continew-starter-cache</module>
<module>continew-starter-auth</module> <module>continew-starter-auth</module>
<module>continew-starter-data</module> <module>continew-starter-data</module>
<module>continew-starter-encrypt</module>
<module>continew-starter-security</module> <module>continew-starter-security</module>
<module>continew-starter-ratelimiter</module> <module>continew-starter-ratelimiter</module>
<module>continew-starter-idempotent</module> <module>continew-starter-idempotent</module>