diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/constant/OrderedConstants.java b/continew-starter-core/src/main/java/top/continew/starter/core/constant/OrderedConstants.java index 1ea1f239..e81410d2 100644 --- a/continew-starter-core/src/main/java/top/continew/starter/core/constant/OrderedConstants.java +++ b/continew-starter-core/src/main/java/top/continew/starter/core/constant/OrderedConstants.java @@ -31,6 +31,11 @@ public class OrderedConstants { */ public static final class Filter { + /** + * API加/密过滤器顺序 + */ + public static final int API_CRYPTO_FILTER = Ordered.HIGHEST_PRECEDENCE; + /** * 链路追踪过滤器顺序 */ diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/constant/PropertiesConstants.java b/continew-starter-core/src/main/java/top/continew/starter/core/constant/PropertiesConstants.java index d1333e5a..dc495ece 100644 --- a/continew-starter-core/src/main/java/top/continew/starter/core/constant/PropertiesConstants.java +++ b/continew-starter-core/src/main/java/top/continew/starter/core/constant/PropertiesConstants.java @@ -55,10 +55,15 @@ public class PropertiesConstants { public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security"; /** - * 安全-加/解密配置 + * 安全-数据加/解密配置 */ public static final String SECURITY_CRYPTO = SECURITY + StringConstants.DOT + "crypto"; + /** + * 安全-API加/解密配置 + */ + public static final String SECURITY_API_CRYPTO = SECURITY + StringConstants.DOT + "api-crypto"; + /** * 安全-敏感词配置 */ diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/ApiEncrypt.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/ApiEncrypt.java new file mode 100644 index 00000000..7d5b1e78 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/annotation/ApiEncrypt.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.annotation; + +import java.lang.annotation.*; + +/** + * API加密注解 + * + * @author lishuyan + * @since 2.14.0 + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiEncrypt { + + /** + * 默认API响应加密 + */ + boolean response() default true; + +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoAutoConfiguration.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoAutoConfiguration.java new file mode 100644 index 00000000..91061cf6 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoAutoConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.autoconfigure; + +import jakarta.annotation.PostConstruct; +import jakarta.servlet.DispatcherType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; +import org.springframework.context.annotation.PropertySource; +import top.continew.starter.core.constant.OrderedConstants; +import top.continew.starter.core.constant.PropertiesConstants; +import top.continew.starter.core.util.GeneralPropertySourceFactory; +import top.continew.starter.security.crypto.filter.ApiCryptoFilter; + +/** + * API加/解密自动配置 + * + * @author lishuyan + * @since 2.14.0 + */ +@AutoConfiguration +@EnableConfigurationProperties(ApiCryptoProperties.class) +@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_API_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true) +@PropertySource(value = "classpath:default-api-crypto.yml", factory = GeneralPropertySourceFactory.class) +public class ApiCryptoAutoConfiguration { + + private static final Logger log = LoggerFactory.getLogger(ApiCryptoAutoConfiguration.class); + + /** + * API加/解密过滤器 + * + * @param properties 配置 + * @return 过滤器 + */ + @Bean + public FilterRegistrationBean apiCryptoFilterRegistration(ApiCryptoProperties properties) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new ApiCryptoFilter(properties)); + registration.addUrlPatterns("/*"); + registration.setName("apiCryptoFilter"); + registration.setOrder(OrderedConstants.Filter.API_CRYPTO_FILTER); + return registration; + } + + @PostConstruct + public void postConstruct() { + log.debug("[ContiNew Starter] - Auto Configuration 'Security-API-Crypto' completed initialization."); + } +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoProperties.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoProperties.java new file mode 100644 index 00000000..3f093c78 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/autoconfigure/ApiCryptoProperties.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import top.continew.starter.core.constant.PropertiesConstants; + +/** + * API加/解密属性配置 + * + * @author lishuyan + * @since 2.14.0 + */ +@ConfigurationProperties(PropertiesConstants.SECURITY_API_CRYPTO) +public class ApiCryptoProperties { + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 请求头中 AES 密钥 键名 + */ + private String secretKeyHeader = "X-Api-Crypto"; + + /** + * 响应加密公钥 + */ + private String publicKey; + + /** + * 请求解密私钥 + */ + private String privateKey; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getSecretKeyHeader() { + return secretKeyHeader; + } + + public void setSecretKeyHeader(String secretKeyHeader) { + this.secretKeyHeader = secretKeyHeader; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ApiCryptoFilter.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ApiCryptoFilter.java new file mode 100644 index 00000000..d6a89fd5 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ApiCryptoFilter.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.filter; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import top.continew.starter.security.crypto.annotation.ApiEncrypt; +import top.continew.starter.security.crypto.autoconfigure.ApiCryptoProperties; + +import java.io.IOException; + +/** + * API加/解密过滤器 + * + * @author lishuyan + * @since 2.14.0 + */ +public class ApiCryptoFilter implements Filter { + + /** + * API加/密配置 + */ + private final ApiCryptoProperties properties; + + public ApiCryptoFilter(ApiCryptoProperties properties) { + this.properties = properties; + } + + @Override + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)servletRequest; + HttpServletResponse response = (HttpServletResponse)servletResponse; + // 获取API加密注解 + ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(request); + // 响应加密标识 + boolean responseEncryptFlag = ObjectUtil.isNotNull(apiEncrypt) && apiEncrypt.response(); + // 密钥标头 + String secretKeyHeader = properties.getSecretKeyHeader(); + ServletRequest requestWrapper = null; + ServletResponse responseWrapper = null; + ResponseBodyEncryptWrapper responseBodyEncryptWrapper = null; + // 是否为 PUT 或者 POST 请求 + if (HttpMethod.PUT.matches(request.getMethod()) || HttpMethod.POST.matches(request.getMethod())) { + // 获取密钥值 + String secretKeyValue = request.getHeader(secretKeyHeader); + if (CharSequenceUtil.isNotBlank(secretKeyValue)) { + // 请求解密 + requestWrapper = new RequestBodyDecryptWrapper(request, properties.getPrivateKey(), secretKeyHeader); + } + } + // 响应加密,响应包装器替换响应体加密包装器 + if (responseEncryptFlag) { + responseBodyEncryptWrapper = new ResponseBodyEncryptWrapper(response); + responseWrapper = responseBodyEncryptWrapper; + } + // 继续执行 + chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), ObjectUtil + .defaultIfNull(responseWrapper, response)); + // 响应加密,执行完成后,响应密文 + if (responseEncryptFlag) { + servletResponse.reset(); + // 获取密文 + String encryptContent = responseBodyEncryptWrapper.getEncryptContent(response, properties + .getPublicKey(), secretKeyHeader); + // 写出密文 + servletResponse.getWriter().write(encryptContent); + } + } + + /** + * 获取 ApiEncrypt 注解 + * + * @param request HTTP请求 + * @return ApiEncrypt注解,如果未找到则返回null + */ + private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest request) { + try { + RequestMappingHandlerMapping handlerMapping = SpringUtil + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + HandlerExecutionChain mappingHandler = handlerMapping.getHandler(request); + // 检查是否存在处理链 + 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; + } + } +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/RequestBodyDecryptWrapper.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/RequestBodyDecryptWrapper.java new file mode 100644 index 00000000..8e1210f2 --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/RequestBodyDecryptWrapper.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.filter; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.springframework.http.MediaType; +import top.continew.starter.security.crypto.util.EncryptHelper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 请求体解密包装类 + * + * @author lishuyan + * @since 2.14.0 + */ +public class RequestBodyDecryptWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public RequestBodyDecryptWrapper(HttpServletRequest request, + String privateKey, + String secretKeyHeader) throws IOException { + super(request); + this.body = getDecryptContent(request, privateKey, secretKeyHeader); + } + + /** + * 获取解密后的请求体 + * + * @param request 请求对象 + * @param privateKey RSA私钥 + * @param secretKeyHeader 密钥头 + * @return 解密后的请求体 + * @throws IOException / + */ + public byte[] getDecryptContent(HttpServletRequest request, + String privateKey, + String secretKeyHeader) throws IOException { + // 通过 请求头 获取 AES 密钥,密钥内容经过 RSA 加密 + String secretKeyByRsa = request.getHeader(secretKeyHeader); + // 通过 RSA 解密,获取 AES 密钥,密钥内容经过 Base64 编码 + String secretKeyByBase64 = EncryptHelper.decryptByRsa(secretKeyByRsa, privateKey); + // 通过 Base64 解码,获取 AES 密钥 + String aesSecretKey = EncryptHelper.decodeByBase64(secretKeyByBase64); + request.setCharacterEncoding(CharsetUtil.UTF_8); + byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false); + String requestBody = new String(readBytes, StandardCharsets.UTF_8); + // 通过 AES 密钥,解密 请求体 + return EncryptHelper.decryptByAes(requestBody, aesSecretKey).getBytes(StandardCharsets.UTF_8); + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public int getContentLength() { + return body.length; + } + + @Override + public long getContentLengthLong() { + return body.length; + } + + @Override + public String getContentType() { + return MediaType.APPLICATION_JSON_VALUE; + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream stream = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() { + return stream.read(); + } + + @Override + public int available() { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ResponseBodyEncryptWrapper.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ResponseBodyEncryptWrapper.java new file mode 100644 index 00000000..2e4755ff --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/filter/ResponseBodyEncryptWrapper.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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.security.crypto.filter; + +import cn.hutool.core.util.CharsetUtil; +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.springframework.http.HttpHeaders; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.security.crypto.util.EncryptHelper; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +/** + * 响应体加密包装类 + * + * @author lishuyan + * @since 2.14.0 + */ +public class ResponseBodyEncryptWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream byteArrayOutputStream; + private final ServletOutputStream servletOutputStream; + private final PrintWriter printWriter; + + public ResponseBodyEncryptWrapper(HttpServletResponse response) throws IOException { + super(response); + this.byteArrayOutputStream = new ByteArrayOutputStream(); + this.servletOutputStream = this.getOutputStream(); + this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream)); + } + + @Override + public PrintWriter getWriter() { + return printWriter; + } + + @Override + public void flushBuffer() throws IOException { + if (servletOutputStream != null) { + servletOutputStream.flush(); + } + if (printWriter != null) { + printWriter.flush(); + } + } + + @Override + public void reset() { + byteArrayOutputStream.reset(); + } + + public byte[] getResponseData() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toByteArray(); + } + + public String getContent() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toString(); + } + + /** + * 获取加密内容 + * + * @param response 响应对象 + * @param publicKey RSA公钥 + * @param secretKeyHeader 密钥头 + * @return 加密内容 + */ + public String getEncryptContent(HttpServletResponse response, + String publicKey, + String secretKeyHeader) throws IOException { + // 生成 AES 密钥 + String aesSecretKey = RandomUtil.randomString(32); + // Base64 编码 + String secretKeyByBase64 = EncryptHelper.encodeByBase64(aesSecretKey); + // RSA 加密 + String secretKeyByRsa = EncryptHelper.encryptByRsa(secretKeyByBase64, publicKey); + // 设置响应头 + response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, secretKeyHeader); + response.setHeader(secretKeyHeader, secretKeyByRsa); + response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, StringConstants.ASTERISK); + response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, StringConstants.ASTERISK); + response.setCharacterEncoding(CharsetUtil.UTF_8); + // 通过 AES 密钥,对原始内容进行加密 + return EncryptHelper.encryptByAes(this.getContent(), aesSecretKey); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return new ServletOutputStream() { + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + + } + + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + byteArrayOutputStream.write(b, off, len); + } + }; + } + +} diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/util/EncryptHelper.java b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/util/EncryptHelper.java index 546ba89a..d891726d 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/util/EncryptHelper.java +++ b/continew-starter-security/continew-starter-security-crypto/src/main/java/top/continew/starter/security/crypto/util/EncryptHelper.java @@ -16,8 +16,13 @@ package top.continew.starter.security.crypto.util; +import cn.hutool.core.codec.Base64; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; 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.LoggerFactory; import top.continew.starter.security.crypto.annotation.FieldEncrypt; @@ -27,6 +32,7 @@ import top.continew.starter.security.crypto.encryptor.IEncryptor; import top.continew.starter.security.crypto.enums.Algorithm; import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -217,4 +223,98 @@ public class EncryptHelper { cryptoContext.setPublicKey(defaultProperties.getPublicKey()); 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); + } } diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-security/continew-starter-security-crypto/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 8ec4466c..f8fe1748 100644 --- a/continew-starter-security/continew-starter-security-crypto/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/continew-starter-security/continew-starter-security-crypto/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ -top.continew.starter.security.crypto.autoconfigure.CryptoAutoConfiguration \ No newline at end of file +top.continew.starter.security.crypto.autoconfigure.CryptoAutoConfiguration +top.continew.starter.security.crypto.autoconfigure.ApiCryptoAutoConfiguration \ No newline at end of file diff --git a/continew-starter-security/continew-starter-security-crypto/src/main/resources/default-api-crypto.yml b/continew-starter-security/continew-starter-security-crypto/src/main/resources/default-api-crypto.yml new file mode 100644 index 00000000..31ecc79d --- /dev/null +++ b/continew-starter-security/continew-starter-security-crypto/src/main/resources/default-api-crypto.yml @@ -0,0 +1,6 @@ +--- ### 安全配置:API加/解密配置 +continew-starter.security: + api-crypto: + enabled: true + # 请求头中 AES 密钥 键名 + secretKeyHeader: X-Api-Crypto \ No newline at end of file