mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 08:57:17 +08:00
refactor(encrypt): 拆分字段加密、API 加密模块
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
<?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-security</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-security-crypto</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>ContiNew Starter 安全模块 - 加密</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security 附带的一个密码加密库 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus(MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-extension</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.annotation;
|
||||
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 字段加/解密注解
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FieldEncrypt {
|
||||
|
||||
/**
|
||||
* 加密/解密算法
|
||||
*/
|
||||
Algorithm value() default Algorithm.DEFAULT;
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
* <p>
|
||||
* 优先级高于加密/解密算法
|
||||
* </p>
|
||||
*/
|
||||
Class<? extends IEncryptor> encryptor() default IEncryptor.class;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
String password() default "";
|
||||
|
||||
/**
|
||||
* 非对称加密算法公钥:RSA需要
|
||||
*/
|
||||
String publicKey() default "";
|
||||
|
||||
/**
|
||||
* 非对称加密算法私钥:RSA需要
|
||||
*/
|
||||
String privateKey() default "";
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.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 top.continew.starter.core.constant.OrderedConstants;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
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)
|
||||
public class ApiCryptoAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiCryptoAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* API加/解密过滤器
|
||||
*
|
||||
* @param properties 配置
|
||||
* @return 过滤器
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<ApiCryptoFilter> apiCryptoFilterRegistration(ApiCryptoProperties properties) {
|
||||
FilterRegistrationBean<ApiCryptoFilter> 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.");
|
||||
}
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* 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.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;
|
||||
}
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.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 org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
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.security.crypto.enums.PasswordEncoderAlgorithm;
|
||||
import top.continew.starter.security.crypto.mybatis.MyBatisDecryptInterceptor;
|
||||
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.Map;
|
||||
|
||||
/**
|
||||
* 加/解密自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(CryptoProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
@PropertySource(value = "classpath:default-crypto.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class CryptoAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码编码器配置
|
||||
*
|
||||
* @see DelegatingPasswordEncoder
|
||||
* @see PasswordEncoderFactories
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO + ".password-encoder", name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
PasswordEncoderProperties passwordEncoderProperties = properties.getPasswordEncoder();
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<>();
|
||||
encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.BCRYPT));
|
||||
encoders.put(PasswordEncoderAlgorithm.SCRYPT.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.SCRYPT));
|
||||
encoders.put(PasswordEncoderAlgorithm.PBKDF2.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.PBKDF2));
|
||||
encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil
|
||||
.getEncoder(PasswordEncoderAlgorithm.ARGON2));
|
||||
PasswordEncoderAlgorithm algorithm = passwordEncoderProperties.getAlgorithm();
|
||||
CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm);
|
||||
return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
EncryptHelper.init(properties);
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Security-Crypto' completed initialization.");
|
||||
}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.autoconfigure;
|
||||
|
||||
import top.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 加密上下文
|
||||
*
|
||||
* @author lishuyan
|
||||
* @since 2.13.2
|
||||
*/
|
||||
public class CryptoContext {
|
||||
|
||||
/**
|
||||
* 默认算法
|
||||
*/
|
||||
private Algorithm algorithm;
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
* <p>
|
||||
* 优先级高于加密/解密算法
|
||||
* </p>
|
||||
*/
|
||||
Class<? extends IEncryptor> encryptor;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 非对称加密算法公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 非对称加密算法私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
public Algorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(Algorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public Class<? extends IEncryptor> getEncryptor() {
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
public void setEncryptor(Class<? extends IEncryptor> encryptor) {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CryptoContext that = (CryptoContext)o;
|
||||
return algorithm == that.algorithm && Objects.equals(encryptor, that.encryptor) && Objects
|
||||
.equals(password, that.password) && Objects.equals(publicKey, that.publicKey) && Objects
|
||||
.equals(privateKey, that.privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(algorithm, encryptor, password, publicKey, privateKey);
|
||||
}
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.crypto.enums.Algorithm;
|
||||
|
||||
/**
|
||||
* 加/解密配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO)
|
||||
public class CryptoProperties {
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 默认算法
|
||||
*/
|
||||
private Algorithm algorithm = Algorithm.AES;
|
||||
|
||||
/**
|
||||
* 对称加密算法密钥
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 非对称加密算法公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 非对称加密算法私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* 密码编码器配置
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private PasswordEncoderProperties passwordEncoder;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Algorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(Algorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public PasswordEncoderProperties getPasswordEncoder() {
|
||||
return passwordEncoder;
|
||||
}
|
||||
|
||||
public void setPasswordEncoder(PasswordEncoderProperties passwordEncoder) {
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.autoconfigure;
|
||||
|
||||
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm;
|
||||
|
||||
/**
|
||||
* 密码编码器配置属性
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class PasswordEncoderProperties {
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 默认启用的编码器算法(默认:BCrypt 加密算法)
|
||||
*/
|
||||
private PasswordEncoderAlgorithm algorithm = PasswordEncoderAlgorithm.BCRYPT;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public PasswordEncoderAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(PasswordEncoderAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* 加密器基类
|
||||
*
|
||||
* @author lishuyan
|
||||
* @since 2.13.2
|
||||
*/
|
||||
public abstract class AbstractEncryptor implements IEncryptor {
|
||||
|
||||
protected AbstractEncryptor(CryptoContext context) {
|
||||
// 配置校验与配置注入
|
||||
}
|
||||
|
||||
}
|
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 对称加/解密处理器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class AbstractSymmetricCryptoEncryptor extends AbstractEncryptor {
|
||||
|
||||
/**
|
||||
* 对称加密缓存
|
||||
*/
|
||||
private static final Map<String, SymmetricCrypto> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 加密上下文
|
||||
*/
|
||||
private final CryptoContext context;
|
||||
|
||||
protected AbstractSymmetricCryptoEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext) {
|
||||
if (CharSequenceUtil.isBlank(plaintext)) {
|
||||
return plaintext;
|
||||
}
|
||||
return this.getCrypto(context.getPassword()).encryptHex(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
if (CharSequenceUtil.isBlank(ciphertext)) {
|
||||
return ciphertext;
|
||||
}
|
||||
return this.getCrypto(context.getPassword()).decryptStr(ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对称加密算法
|
||||
*
|
||||
* @param password 密钥
|
||||
* @return 对称加密算法
|
||||
*/
|
||||
protected SymmetricCrypto getCrypto(String password) {
|
||||
SymmetricAlgorithm algorithm = this.getAlgorithm();
|
||||
String key = algorithm + StringConstants.UNDERLINE + password;
|
||||
if (CACHE.containsKey(key)) {
|
||||
return CACHE.get(key);
|
||||
}
|
||||
SymmetricCrypto symmetricCrypto = new SymmetricCrypto(algorithm, password.getBytes(StandardCharsets.UTF_8));
|
||||
CACHE.put(key, symmetricCrypto);
|
||||
return symmetricCrypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对称加密算法类型
|
||||
*
|
||||
* @return 对称加密算法类型
|
||||
*/
|
||||
protected abstract SymmetricAlgorithm getAlgorithm();
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* AES(Advanced Encryption Standard) 加/解密处理器
|
||||
* <p>
|
||||
* 美国国家标准与技术研究院(NIST)采纳的对称加密算法标准,提供128位、192位和256位三种密钥长度,以高效和安全性著称。
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class AesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public AesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.AES;
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
|
||||
/**
|
||||
* Base64 加/解密处理器
|
||||
* <p>
|
||||
* 一种用于编码二进制数据到文本格式的算法,常用于邮件附件、网页传输等场合,但它不是一种加密算法,只提供数据的编码和解码,不保证数据的安全性。
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class Base64Encryptor implements IEncryptor {
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext) {
|
||||
return Base64.encode(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
return Base64.decodeStr(ciphertext);
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* DES(Data Encryption Standard) 加/解密处理器
|
||||
* <p>
|
||||
* 一种对称加密算法,使用相同的密钥进行加密和解密。DES 使用 56 位密钥(实际上有 64 位,但有 8 位用于奇偶校验)和一系列置换和替换操作来加密数据。
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class DesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public DesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.DES;
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
/**
|
||||
* 加/解密接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface IEncryptor {
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param plaintext 明文
|
||||
* @return 加密后的文本
|
||||
*/
|
||||
String encrypt(String plaintext);
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param ciphertext 密文
|
||||
* @return 解密后的文本
|
||||
*/
|
||||
String decrypt(String ciphertext);
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
import top.continew.starter.security.crypto.autoconfigure.PasswordEncoderProperties;
|
||||
|
||||
/**
|
||||
* 密码编码器加/解密处理器
|
||||
*
|
||||
* <p>
|
||||
* 使用前必须注入 {@link PasswordEncoder},此加密方式不可逆,适合于密码场景
|
||||
* </p>
|
||||
*
|
||||
* @see PasswordEncoder
|
||||
* @see PasswordEncoderProperties
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class PasswordEncoderEncryptor extends AbstractEncryptor {
|
||||
|
||||
private final PasswordEncoder passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);
|
||||
private final CryptoProperties properties = SpringUtil.getBean(CryptoProperties.class);
|
||||
|
||||
public PasswordEncoderEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext) {
|
||||
// 如果已经是加密格式,直接返回
|
||||
if (properties.getPasswordEncoder().getAlgorithm().getPattern().matcher(plaintext).matches()) {
|
||||
return plaintext;
|
||||
}
|
||||
return passwordEncoder.encode(plaintext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
return ciphertext;
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* PBEWithMD5AndDES(Password Based Encryption With MD5 And DES) 加/解密处理器
|
||||
* <p>
|
||||
* 混合加密算法,结合了 MD5 散列算法和 DES(Data Encryption Standard)加密算法
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class PbeWithMd5AndDesEncryptor extends AbstractSymmetricCryptoEncryptor {
|
||||
|
||||
public PbeWithMd5AndDesEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SymmetricAlgorithm getAlgorithm() {
|
||||
return SymmetricAlgorithm.PBEWithMD5AndDES;
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.encryptor;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
|
||||
/**
|
||||
* RSA 加/解密处理器
|
||||
* <p>
|
||||
* 非对称加密算法,由罗纳德·李维斯特(Ron Rivest)、阿迪·沙米尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)于1977年提出,安全性基于大数因子分解问题的困难性。
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class RsaEncryptor extends AbstractEncryptor {
|
||||
|
||||
/**
|
||||
* 加密上下文
|
||||
*/
|
||||
private final CryptoContext context;
|
||||
|
||||
public RsaEncryptor(CryptoContext context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encrypt(String plaintext) {
|
||||
return Base64.encode(SecureUtil.rsa(null, context.getPublicKey()).encrypt(plaintext, KeyType.PublicKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String ciphertext) {
|
||||
return new String(SecureUtil.rsa(context.getPrivateKey(), null)
|
||||
.decrypt(Base64.decode(ciphertext), KeyType.PrivateKey));
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.enums;
|
||||
|
||||
import top.continew.starter.security.crypto.encryptor.*;
|
||||
|
||||
/**
|
||||
* 加密/解密算法枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public enum Algorithm {
|
||||
|
||||
/**
|
||||
* 默认使用配置属性的算法
|
||||
*/
|
||||
DEFAULT(null),
|
||||
|
||||
/**
|
||||
* AES
|
||||
*/
|
||||
AES(AesEncryptor.class),
|
||||
|
||||
/**
|
||||
* DES
|
||||
*/
|
||||
DES(DesEncryptor.class),
|
||||
|
||||
/**
|
||||
* PBE With MD5 And DES
|
||||
*/
|
||||
PBE_WITH_MD5_AND_DES(PbeWithMd5AndDesEncryptor.class),
|
||||
|
||||
/**
|
||||
* RSA
|
||||
*/
|
||||
RSA(RsaEncryptor.class),
|
||||
|
||||
/**
|
||||
* Base64
|
||||
*/
|
||||
BASE64(Base64Encryptor.class),
|
||||
|
||||
/**
|
||||
* 密码编码器,支持算法:BCrypt、SCRYPT、PBKDF2、ARGON2
|
||||
*/
|
||||
PASSWORD_ENCODER(PasswordEncoderEncryptor.class);
|
||||
|
||||
/**
|
||||
* 加密/解密处理器
|
||||
*/
|
||||
private final Class<? extends IEncryptor> encryptor;
|
||||
|
||||
Algorithm(Class<? extends IEncryptor> encryptor) {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
public Class<? extends IEncryptor> getEncryptor() {
|
||||
return encryptor;
|
||||
}
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.enums;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 密码编码器加密算法枚举
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public enum PasswordEncoderAlgorithm {
|
||||
|
||||
/** BCrypt加密算法 */
|
||||
BCRYPT(Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}")),
|
||||
|
||||
/** SCrypt加密算法 */
|
||||
SCRYPT(Pattern.compile("\\A\\$s0\\$[0-9a-f]+\\$[0-9a-f]+\\$[0-9a-f]+")),
|
||||
|
||||
/** PBKDF2加密算法 */
|
||||
PBKDF2(Pattern.compile("\\A\\$pbkdf2-sha256\\$\\d+\\$[0-9a-f]+\\$[0-9a-f]+")),
|
||||
|
||||
/** Argon2加密算法 */
|
||||
ARGON2(Pattern.compile("\\A\\$argon2(id|i|d)\\$v=\\d+\\$m=\\d+,t=\\d+,p=\\d+\\$[0-9a-zA-Z+/]+\\$[0-9a-zA-Z+/]+"));
|
||||
|
||||
/** 正则匹配 */
|
||||
private final Pattern pattern;
|
||||
|
||||
PasswordEncoderAlgorithm(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Pattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.exception;
|
||||
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 密码编码异常
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public class PasswordEncodeException extends BaseException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PasswordEncodeException() {
|
||||
}
|
||||
|
||||
public PasswordEncodeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PasswordEncodeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PasswordEncodeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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.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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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.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) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* 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.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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 字段解密拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class AbstractMyBatisInterceptor {
|
||||
|
||||
private static final Map<Class<?>, List<Field>> CLASS_FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取所有字符串类型、需要加/解密的、有值字段
|
||||
*
|
||||
* @param obj 对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
protected List<Field> getEncryptFields(Object obj) {
|
||||
if (obj == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.getEncryptFields(obj.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有字符串类型、需要加/解密的、有值字段
|
||||
*
|
||||
* @param clazz 类型对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
protected List<Field> getEncryptFields(Class<?> clazz) {
|
||||
return CLASS_FIELD_CACHE.computeIfAbsent(clazz, key -> Arrays.stream(ReflectUtil.getFields(clazz))
|
||||
.filter(field -> String.class.equals(field.getType()))
|
||||
.filter(field -> field.getAnnotation(FieldEncrypt.class) != null)
|
||||
.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密参数
|
||||
*
|
||||
* @param mappedStatement 映射语句
|
||||
* @return 获取加密参数
|
||||
*/
|
||||
protected Map<String, FieldEncrypt> getEncryptParameters(MappedStatement mappedStatement) {
|
||||
String mappedStatementId = mappedStatement.getId();
|
||||
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> {
|
||||
Method method = this.getMethod(mappedStatementId);
|
||||
if (method == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, FieldEncrypt> encryptMap = new HashMap<>();
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
|
||||
if (fieldEncrypt == null) {
|
||||
continue;
|
||||
}
|
||||
String parameterName = this.getParameterName(parameter);
|
||||
encryptMap.put(parameterName, fieldEncrypt);
|
||||
if (String.class.equals(parameter.getType())) {
|
||||
encryptMap.put("param" + (i + 1), fieldEncrypt);
|
||||
}
|
||||
}
|
||||
return encryptMap;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射方法
|
||||
*
|
||||
* @param mappedStatementId 映射语句 ID
|
||||
* @return 映射方法
|
||||
*/
|
||||
private Method getMethod(String mappedStatementId) {
|
||||
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
|
||||
String methodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
|
||||
try {
|
||||
Method[] methods = ReflectUtil.getMethods(Class.forName(className));
|
||||
return Stream.of(methods).filter(method -> method.getName().equals(methodName)).findFirst().orElse(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new BaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数名称
|
||||
*
|
||||
* @param parameter 参数
|
||||
* @return 参数名称
|
||||
*/
|
||||
public String getParameterName(Parameter parameter) {
|
||||
Param param = parameter.getAnnotation(Param.class);
|
||||
return param != null ? param.value() : parameter.getName();
|
||||
}
|
||||
}
|
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
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.apache.ibatis.type.SimpleTypeRegistry;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.util.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字段解密拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
|
||||
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor {
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
// 获取执行结果
|
||||
Object obj = invocation.proceed();
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
// 确保目标是 ResultSetHandler
|
||||
if (!(invocation.getTarget() instanceof ResultSetHandler)) {
|
||||
return obj;
|
||||
}
|
||||
// 处理查询结果
|
||||
if (obj instanceof List<?> resultList) {
|
||||
// 处理列表结果
|
||||
this.decryptList(resultList);
|
||||
} else if (obj instanceof Map<?, ?> map) {
|
||||
// 处理Map结果
|
||||
this.decryptMap(map);
|
||||
} else {
|
||||
// 处理单个对象结果
|
||||
this.decryptObject(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密列表结果
|
||||
*
|
||||
* @param resultList 结果列表
|
||||
*/
|
||||
private void decryptList(List<?> resultList) {
|
||||
if (CollUtil.isEmpty(resultList)) {
|
||||
return;
|
||||
}
|
||||
for (Object result : resultList) {
|
||||
decryptObject(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密Map结果
|
||||
*
|
||||
* @param resultMap 结果Map
|
||||
*/
|
||||
private void decryptMap(Map<?, ?> resultMap) {
|
||||
if (CollUtil.isEmpty(resultMap)) {
|
||||
return;
|
||||
}
|
||||
new HashSet<>(resultMap.values()).forEach(this::decryptObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密单个对象结果
|
||||
*
|
||||
* @param result 结果对象
|
||||
*/
|
||||
private void decryptObject(Object result) {
|
||||
if (ObjectUtil.isNull(result)) {
|
||||
return;
|
||||
}
|
||||
// String、Integer、Long 等简单类型对象无需处理
|
||||
if (SimpleTypeRegistry.isSimpleType(result.getClass())) {
|
||||
return;
|
||||
}
|
||||
// 获取所有字符串类型、需要解密的、有值字段
|
||||
List<Field> fieldList = super.getEncryptFields(result);
|
||||
if (fieldList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 解密处理
|
||||
for (Field field : fieldList) {
|
||||
Object fieldValue = ReflectUtil.getFieldValue(result, field);
|
||||
if (fieldValue == null) {
|
||||
continue;
|
||||
}
|
||||
String strValue = String.valueOf(fieldValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
continue;
|
||||
}
|
||||
ReflectUtil.setFieldValue(result, field, EncryptHelper.decrypt(strValue, field
|
||||
.getAnnotation(FieldEncrypt.class)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.mybatis;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
|
||||
import top.continew.starter.security.crypto.util.EncryptHelper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 字段加密拦截器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author lishuyan
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
|
||||
|
||||
private static final Pattern PARAM_PAIRS_PATTERN = Pattern
|
||||
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
|
||||
|
||||
@Override
|
||||
public void beforeQuery(Executor executor,
|
||||
MappedStatement mappedStatement,
|
||||
Object parameterObject,
|
||||
RowBounds rowBounds,
|
||||
ResultHandler resultHandler,
|
||||
BoundSql boundSql) {
|
||||
if (parameterObject == null) {
|
||||
return;
|
||||
}
|
||||
if (parameterObject instanceof Map parameterMap) {
|
||||
this.encryptQueryParameter(parameterMap, mappedStatement);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) {
|
||||
if (parameterObject == null) {
|
||||
return;
|
||||
}
|
||||
if (parameterObject instanceof Map parameterMap) {
|
||||
// 带别名方法(使用 @Param 注解的场景)
|
||||
this.encryptMap(parameterMap, mappedStatement);
|
||||
} else {
|
||||
// 无别名方法(例如:MP insert 等方法)
|
||||
this.encryptEntity(super.getEncryptFields(parameterObject), parameterObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密查询参数(针对 Map 类型参数)
|
||||
*
|
||||
* @param parameterMap 参数
|
||||
* @param mappedStatement 映射语句
|
||||
*/
|
||||
private void encryptQueryParameter(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
|
||||
Map<String, FieldEncrypt> encryptParameterMap = super.getEncryptParameters(mappedStatement);
|
||||
for (Map.Entry<String, Object> parameterEntrySet : parameterMap.entrySet()) {
|
||||
String parameterName = parameterEntrySet.getKey();
|
||||
Object parameterValue = parameterEntrySet.getValue();
|
||||
if (parameterValue == null || ClassUtil.isBasicType(parameterValue
|
||||
.getClass()) || parameterValue instanceof AbstractWrapper) {
|
||||
continue;
|
||||
}
|
||||
if (parameterValue instanceof String str) {
|
||||
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
|
||||
if (fieldEncrypt != null) {
|
||||
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
|
||||
}
|
||||
} else {
|
||||
// 实体参数
|
||||
this.encryptEntity(super.getEncryptFields(parameterValue), parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体加密
|
||||
*
|
||||
* @param fieldList 加密字段列表
|
||||
* @param entity 实体
|
||||
*/
|
||||
private void encryptEntity(List<Field> fieldList, Object entity) {
|
||||
for (Field field : fieldList) {
|
||||
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
|
||||
if (fieldValue == null) {
|
||||
continue;
|
||||
}
|
||||
String strValue = String.valueOf(fieldValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
continue;
|
||||
}
|
||||
ReflectUtil.setFieldValue(entity, field, EncryptHelper.encrypt(strValue, field
|
||||
.getAnnotation(FieldEncrypt.class)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密 Map 类型数据(使用 @Param 注解的场景)
|
||||
*
|
||||
* @param parameterMap 参数
|
||||
* @param mappedStatement 映射语句
|
||||
*/
|
||||
private void encryptMap(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
|
||||
Object parameter;
|
||||
// 别名带有 et(针对 MP 的 updateById、update 等方法)
|
||||
if (parameterMap.containsKey(Constants.ENTITY) && (parameter = parameterMap.get(Constants.ENTITY)) != null) {
|
||||
this.encryptEntity(super.getEncryptFields(parameter), parameter);
|
||||
}
|
||||
// 别名带有 ew(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
|
||||
if (parameterMap.containsKey(Constants.WRAPPER) && (parameter = parameterMap.get(Constants.WRAPPER)) != null) {
|
||||
this.encryptUpdateWrapper(parameter, mappedStatement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 UpdateWrapper 类型参数加密(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
|
||||
*
|
||||
* @param parameter Wrapper 参数
|
||||
* @param mappedStatement 映射语句
|
||||
* @author cary
|
||||
* @author wangshaopeng@talkweb.com.cn(<a
|
||||
* href="https://blog.csdn.net/tianmaxingkonger/article/details/130986784">基于Mybatis-Plus拦截器实现MySQL数据加解密</a>)
|
||||
* @since 2.1.1
|
||||
*/
|
||||
private void encryptUpdateWrapper(Object parameter, MappedStatement mappedStatement) {
|
||||
if (parameter instanceof AbstractWrapper updateWrapper) {
|
||||
String sqlSet = updateWrapper.getSqlSet();
|
||||
if (CharSequenceUtil.isBlank(sqlSet)) {
|
||||
return;
|
||||
}
|
||||
// 将 name=#{ew.paramNameValuePairs.xxx},age=#{ew.paramNameValuePairs.xxx} 切出来
|
||||
String[] elArr = sqlSet.split(StringConstants.COMMA);
|
||||
Map<String, String> propMap = new HashMap<>(elArr.length);
|
||||
Arrays.stream(elArr).forEach(el -> {
|
||||
String[] elPart = el.split(StringConstants.EQUALS);
|
||||
propMap.put(elPart[0], elPart[1]);
|
||||
});
|
||||
// 获取加密字段
|
||||
Class<?> entityClass = this.getEntityClass(updateWrapper, mappedStatement);
|
||||
List<Field> encryptFieldList = super.getEncryptFields(entityClass);
|
||||
for (Field field : encryptFieldList) {
|
||||
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
|
||||
String el = propMap.get(field.getName());
|
||||
if (CharSequenceUtil.isBlank(el)) {
|
||||
continue;
|
||||
}
|
||||
Matcher matcher = PARAM_PAIRS_PATTERN.matcher(el);
|
||||
if (matcher.matches()) {
|
||||
String valueKey = matcher.group(1);
|
||||
Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
|
||||
updateWrapper.getParamNameValuePairs().put(valueKey, this.doEncrypt(value, fieldEncrypt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理加密
|
||||
*
|
||||
* @param parameterValue 参数值
|
||||
* @param fieldEncrypt 字段加密注解
|
||||
*/
|
||||
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
|
||||
if (ObjectUtil.isNull(parameterValue)) {
|
||||
return null;
|
||||
}
|
||||
String strValue = String.valueOf(parameterValue);
|
||||
if (CharSequenceUtil.isBlank(strValue)) {
|
||||
return null;
|
||||
}
|
||||
return EncryptHelper.encrypt(strValue, fieldEncrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体类
|
||||
*
|
||||
* @param wrapper 查询或更新包装器
|
||||
* @param mappedStatement 映射语句
|
||||
* @return 实体类
|
||||
*/
|
||||
private Class<?> getEntityClass(AbstractWrapper wrapper, MappedStatement mappedStatement) {
|
||||
// 尝试从 Wrapper 中获取实体类
|
||||
Class<?> entityClass = wrapper.getEntityClass();
|
||||
if (entityClass != null) {
|
||||
return entityClass;
|
||||
}
|
||||
// 从映射语句中获取实体类
|
||||
return mappedStatement.getParameterMap().getType();
|
||||
}
|
||||
}
|
@@ -1,320 +0,0 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
|
||||
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 加密助手
|
||||
*
|
||||
* @author lishuyan
|
||||
* @since 2.13.2
|
||||
*/
|
||||
public class EncryptHelper {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EncryptHelper.class);
|
||||
|
||||
/**
|
||||
* 默认加密配置
|
||||
*/
|
||||
private static CryptoProperties defaultProperties;
|
||||
|
||||
/**
|
||||
* 加密器缓存
|
||||
*/
|
||||
private static final Map<Integer, IEncryptor> ENCRYPTOR_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private EncryptHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认配置
|
||||
*
|
||||
* @param properties 加密配置
|
||||
*/
|
||||
public static void init(CryptoProperties properties) {
|
||||
defaultProperties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册加密执行者到缓存
|
||||
* <p>
|
||||
* 计算 CryptoContext 对象的hashCode作为缓存中的key,通过hashCode查询缓存中存在则直接返回,不存在则创建并缓存
|
||||
* </p>
|
||||
*
|
||||
* @param cryptoContext 加密执行者需要的相关配置参数
|
||||
* @return 加密执行者
|
||||
*/
|
||||
public static IEncryptor registerAndGetEncryptor(CryptoContext cryptoContext) {
|
||||
int key = cryptoContext.hashCode();
|
||||
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> cryptoContext.getEncryptor().equals(IEncryptor.class)
|
||||
? ReflectUtil.newInstance(cryptoContext.getAlgorithm().getEncryptor(), cryptoContext)
|
||||
: ReflectUtil.newInstance(cryptoContext.getEncryptor(), cryptoContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段上的 @FieldEncrypt 注解
|
||||
*
|
||||
* @param obj 对象
|
||||
* @param fieldName 字段名称
|
||||
* @return 字段上的 @FieldEncrypt 注解
|
||||
* @throws NoSuchFieldException /
|
||||
*/
|
||||
public static FieldEncrypt getFieldEncrypt(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Field field = obj.getClass().getDeclaredField(fieldName);
|
||||
return field.getAnnotation(FieldEncrypt.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @param fieldEncrypt 待加密字段注解
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String encrypt(String value, FieldEncrypt fieldEncrypt) {
|
||||
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String ciphertext = value;
|
||||
try {
|
||||
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
|
||||
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
|
||||
ciphertext = encryptor.encrypt(ciphertext);
|
||||
} catch (Exception e) {
|
||||
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
*
|
||||
* @param value 待加密字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String encrypt(String value) {
|
||||
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String ciphertext = value;
|
||||
try {
|
||||
CryptoContext cryptoContext = buildCryptoContext();
|
||||
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
|
||||
ciphertext = encryptor.encrypt(ciphertext);
|
||||
} catch (Exception e) {
|
||||
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
*
|
||||
* @param value 待解密字符串
|
||||
* @param fieldEncrypt 待解密字段注解
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decrypt(String value, FieldEncrypt fieldEncrypt) {
|
||||
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String plaintext = value;
|
||||
try {
|
||||
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
|
||||
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
|
||||
plaintext = encryptor.decrypt(plaintext);
|
||||
} catch (Exception e) {
|
||||
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
*
|
||||
* @param value 待解密字符串
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decrypt(String value) {
|
||||
if (CharSequenceUtil.isBlank(value) || !defaultProperties.isEnabled()) {
|
||||
return value;
|
||||
}
|
||||
String plaintext = value;
|
||||
try {
|
||||
CryptoContext cryptoContext = buildCryptoContext();
|
||||
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
|
||||
plaintext = encryptor.decrypt(plaintext);
|
||||
} catch (Exception e) {
|
||||
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建加密上下文
|
||||
*
|
||||
* @param fieldEncrypt 字段加密注解
|
||||
* @return 加密上下文
|
||||
*/
|
||||
private static CryptoContext buildCryptoContext(FieldEncrypt fieldEncrypt) {
|
||||
CryptoContext cryptoContext = new CryptoContext();
|
||||
cryptoContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
|
||||
? defaultProperties.getAlgorithm()
|
||||
: fieldEncrypt.value());
|
||||
cryptoContext.setEncryptor(fieldEncrypt.encryptor().equals(IEncryptor.class)
|
||||
? IEncryptor.class
|
||||
: fieldEncrypt.encryptor());
|
||||
cryptoContext.setPassword(fieldEncrypt.password().isEmpty()
|
||||
? defaultProperties.getPassword()
|
||||
: fieldEncrypt.password());
|
||||
cryptoContext.setPrivateKey(fieldEncrypt.privateKey().isEmpty()
|
||||
? defaultProperties.getPrivateKey()
|
||||
: fieldEncrypt.privateKey());
|
||||
cryptoContext.setPublicKey(fieldEncrypt.publicKey().isEmpty()
|
||||
? defaultProperties.getPublicKey()
|
||||
: fieldEncrypt.publicKey());
|
||||
return cryptoContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建加密上下文
|
||||
*
|
||||
* @return 加密上下文
|
||||
*/
|
||||
private static CryptoContext buildCryptoContext() {
|
||||
CryptoContext cryptoContext = new CryptoContext();
|
||||
cryptoContext.setAlgorithm(defaultProperties.getAlgorithm());
|
||||
cryptoContext.setEncryptor(IEncryptor.class);
|
||||
cryptoContext.setPassword(defaultProperties.getPassword());
|
||||
cryptoContext.setPrivateKey(defaultProperties.getPrivateKey());
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* 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.security.crypto.util;
|
||||
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm;
|
||||
import top.continew.starter.security.crypto.exception.PasswordEncodeException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 密码加密工具类
|
||||
* <p>
|
||||
* 支持多种加密算法,可通过编码ID动态选择加密方式
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.13.3
|
||||
*/
|
||||
public final class PasswordEncoderUtil {
|
||||
|
||||
private static final Map<PasswordEncoderAlgorithm, PasswordEncoder> ENCODER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
// 初始化默认的加密算法实例
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.BCRYPT, new BCryptPasswordEncoder());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.SCRYPT, SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.PBKDF2, Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
ENCODER_CACHE.put(PasswordEncoderAlgorithm.ARGON2, Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
|
||||
}
|
||||
|
||||
private PasswordEncoderUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的加密算法加密密码
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @param rawPassword 原始密码
|
||||
* @return 加密后的密码
|
||||
* @throws IllegalArgumentException 如果不支持指定的加密算法
|
||||
*/
|
||||
public static String encode(PasswordEncoderAlgorithm algorithm, String rawPassword) {
|
||||
// 参数校验
|
||||
if (algorithm == null) {
|
||||
throw new IllegalArgumentException("加密算法不能为空");
|
||||
}
|
||||
if (rawPassword == null) {
|
||||
throw new IllegalArgumentException("原始密码不能为空");
|
||||
}
|
||||
|
||||
// 获取对应的密码编码器
|
||||
PasswordEncoder encoder = ENCODER_CACHE.get(algorithm);
|
||||
if (encoder == null) {
|
||||
throw new IllegalArgumentException("不支持的加密算法: " + algorithm);
|
||||
}
|
||||
|
||||
try {
|
||||
return encoder.encode(rawPassword);
|
||||
} catch (Exception e) {
|
||||
throw new PasswordEncodeException("密码加密失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码是否匹配
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @param rawPassword 原始密码
|
||||
* @param encodedPassword 加密后的密码
|
||||
* @return 是否匹配
|
||||
* @throws IllegalArgumentException 如果不支持指定的加密算法
|
||||
*/
|
||||
public static boolean matches(PasswordEncoderAlgorithm algorithm, String rawPassword, String encodedPassword) {
|
||||
// 参数校验
|
||||
if (algorithm == null) {
|
||||
throw new IllegalArgumentException("加密算法不能为空");
|
||||
}
|
||||
if (rawPassword == null || encodedPassword == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取对应的密码编码器
|
||||
PasswordEncoder encoder = ENCODER_CACHE.get(algorithm);
|
||||
if (encoder == null) {
|
||||
throw new IllegalArgumentException("不支持的加密算法: " + algorithm);
|
||||
}
|
||||
|
||||
try {
|
||||
return encoder.matches(rawPassword, encodedPassword);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定算法的密码编码器
|
||||
*
|
||||
* @param algorithm 加密算法
|
||||
* @return 密码编码器实例,不存在则返回null
|
||||
*/
|
||||
public static PasswordEncoder getEncoder(PasswordEncoderAlgorithm algorithm) {
|
||||
if (algorithm == null) {
|
||||
return null;
|
||||
}
|
||||
return ENCODER_CACHE.get(algorithm);
|
||||
}
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
top.continew.starter.security.crypto.autoconfigure.CryptoAutoConfiguration
|
||||
top.continew.starter.security.crypto.autoconfigure.ApiCryptoAutoConfiguration
|
@@ -1,6 +0,0 @@
|
||||
--- ### 安全配置:字段加/解密配置
|
||||
continew-starter.security:
|
||||
crypto:
|
||||
enabled: true
|
||||
# 默认算法,即 @FieldEncrypt 默认采用的算法(默认:AES 对称加密算法)
|
||||
algorithm: AES
|
@@ -16,7 +16,6 @@
|
||||
<description>ContiNew Starter 安全模块</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-security-crypto</module>
|
||||
<module>continew-starter-security-mask</module>
|
||||
<module>continew-starter-security-xss</module>
|
||||
<module>continew-starter-security-sensitivewords</module>
|
||||
|
Reference in New Issue
Block a user