Seven Data Security is a comprehensive Spring Boot library for MyBatis that provides transparent data encryption, desensitization, and fine-grained data scope control for enterprise applications. It seamlessly integrates with MyBatis/MyBatis-Plus to automatically handle sensitive data protection without modifying business logic.
- Features
- Architecture
- Quick Start
- Core Functionality
- Configuration
- Advanced Usage
- Design Patterns
- Performance Considerations
- Contributing
- License
- Transparent encryption/decryption via annotations
- Multiple encryption algorithms support (DES, AES, custom algorithms)
- Zero business logic impact - encryption handled by interceptors
- Type-safe Encrypt wrapper type for explicit encryption
- Automatic parameter detection and encryption
- Support for multiple parameter types: Map, Object, MyBatis-Plus QueryWrapper
- Smart field matching with camelCase and snake_case conversion
- Thread-safe parameter restoration after SQL execution
- Row-level data filtering based on user permissions
- Flexible strategy pattern for custom access rules
- SQL rewriting with JOIN and WHERE conditions
- Multi-tenant support for SaaS applications
- SQL statement printing with actual parameter values
- Execution time tracking for performance monitoring
- Environment-aware (disabled in production)
- Single table query optimization with automatic table prefix
- SQL rewriting for data scope injection
- Smart column detection and processing
- Thread-safe operations with ThreadLocal context
- Lazy initialization with caching for performance
- Spring Boot auto-configuration
- Extensible architecture via interfaces and abstract classes
seven-data-security
βββ config # Auto-configuration classes
β βββ JacksonConfig # JSON serialization config
β βββ MaskAutoConfig # Main auto-config
β βββ MyBatisInterceptorAutoConfig # MyBatis interceptors setup
βββ domain # Core domain models
β βββ AnnotatedField # Field annotation metadata
β βββ Encrypt # Encryption wrapper type
β βββ EncryptInfo # Parameter encryption context
β βββ RestoreInfo # Parameter restoration context
βββ encrypt # Encryption subsystem
β βββ annotation # @EncryptField annotation
β βββ container # Algorithm and metadata containers
β βββ context # Encryption context management
β βββ jackson # JSON serialization support
β βββ processor # Encryption/decryption processors
β β βββ DecryptProcessor # Result decryption
β β βββ EncryptProcessor # Parameter encryption
β β βββ SingleSelectProcessor # Single table SELECT optimization
β βββ shield # Encryption algorithm implementations
β βββ type/handler # MyBatis type handler
βββ interceptor # MyBatis interceptors
β βββ DecryptInterceptor # Result decryption
β βββ SqlPrintInterceptor # SQL logging
β βββ SqlRewriteInterceptor # Parameter encryption & data scope
βββ kit # Utility classes
β βββ ClazzUtil # Reflection utilities
β βββ FieldMatchUtil # Field matching logic
β βββ ParamUtil # Parameter processing
β βββ SpringContextUtil # Spring context access
β βββ SqlPrint # SQL formatting
β βββ StringUtil # String operations
βββ scope # Data scope subsystem
β βββ DataScopeHelper # Scope context management
β βββ DataScopeStrategy # Strategy interface
β βββ EmptyDataScopeStrategy # Empty strategy implementation
β βββ container # Strategy container
β βββ processor # SQL rewriting processor
βββ exception # Exception hierarchy
βββ DataSecurityException # Base exception
βββ JacksonException # JSON-related exception
Maven:
<dependency>
<groupId>io.github.qwzhang01</groupId>
<artifactId>seven-data-security</artifactId>
<version>1.2.21</version>
</dependency>Gradle:
implementation 'io.github.qwzhang01:seven-data-security:1.2.21'@Data
@TableName("user")
public class User {
@TableId
private Long id;
private String username;
// Automatically encrypted/decrypted
@EncryptField
private String phoneNumber;
@EncryptField
private String email;
// Custom encryption algorithm
@EncryptField(CustomAesAlgo.class)
private String socialSecurityNumber;
}@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// Data is automatically encrypted before insert
public void createUser(User user) {
userMapper.insert(user);
}
// Data is automatically decrypted after query
public User getUser(Long id) {
return userMapper.selectById(id);
}
// Query parameters are automatically encrypted
public List<User> findByPhone(String phone) {
return userMapper.selectList(
new QueryWrapper<User>().eq("phone_number", phone)
);
}
}That's it! No additional code changes are required. The library automatically handles encryption and decryption.
@Data
@TableName("customer")
public class Customer {
@TableId
private Long id;
// Use default encryption algorithm
@EncryptField
private String phoneNumber;
// Specify custom encryption algorithm
@EncryptField(AesEncryptionAlgo.class)
private String creditCard;
private String name;
}@Data
@TableName("sensitive_data")
public class SensitiveData {
@TableId
private Long id;
// Type-safe encryption wrapper
private Encrypt secretData;
// Getter returns plain text
public String getSecretData() {
return secretData.getValue();
}
// Setter accepts plain text
public void setSecretData(String value) {
this.secretData = new Encrypt(value);
}
}public class AesEncryptionAlgo implements EncryptionAlgo {
private static final String KEY = "YourSecretKey123"; // Use environment variable in production
@Override
public String encrypt(String value) {
if (value == null) return null;
// Implement AES encryption
return AesUtils.encrypt(value, KEY);
}
@Override
public String decrypt(String value) {
if (value == null) return null;
// Implement AES decryption
return AesUtils.decrypt(value, KEY);
}
}@Configuration
public class SecurityConfig {
@Bean
public AesEncryptionAlgo aesEncryptionAlgo() {
return new AesEncryptionAlgo();
}
}The library automatically encrypts query parameters that match encrypted fields:
// All these queries automatically encrypt the phone parameter
public class UserService {
// Map parameters
public List<User> findByPhone(String phone) {
Map<String, Object> params = new HashMap<>();
params.put("phoneNumber", phone);
return userMapper.selectByMap(params);
}
// Object parameters
public List<User> findUsers(UserQuery query) {
return userMapper.selectList(query); // query.phoneNumber is auto-encrypted
}
// QueryWrapper parameters (MyBatis-Plus)
public List<User> findWithWrapper(String phone) {
return userMapper.selectList(
new QueryWrapper<User>().eq("phone_number", phone) // auto-encrypted
);
}
// XML Mapper parameters
public List<User> findInXml(String phone) {
return userMapper.findByPhone(phone); // auto-encrypted
}
}XML Mapper Example:
<select id="findByPhone" resultType="User">
SELECT * FROM user WHERE phone_number = #{phoneNumber}
<!-- phoneNumber is automatically encrypted before execution -->
</select>The library automatically adds table prefix to columns in single-table SELECT queries, which helps prevent column name conflicts when used with data scope SQL rewriting:
// Original query
String sql = "SELECT id, name FROM user WHERE status = 1";
// After processing
String processedSql = "SELECT user.id, user.name FROM user WHERE user.status = 1";Features:
- β Automatically detects single-table SELECT queries
- β Adds table prefix to SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING
- β
Handles
SELECT *βSELECT table.*conversion - β Respects table aliases
- β Skips queries with JOIN clauses (not single-table)
Use Cases:
- Prevent ambiguous column names when data scope adds JOIN clauses
- Improve SQL compatibility with complex query rewrites
- Ensure consistent column references across different query types
Implement fine-grained data access control based on user permissions:
@Component
public class DepartmentDataScopeStrategy implements DataScopeStrategy<Long> {
@Autowired
private UserContext userContext;
@Override
public String join() {
// Add JOIN clause if needed
return "LEFT JOIN department d ON t.dept_id = d.id";
}
@Override
public String where() {
// Add WHERE clause for permission filtering
List<Long> deptIds = userContext.getUserDepartmentIds();
return "d.id IN (" + StringUtils.join(deptIds, ",") + ")";
}
@Override
public void validDs(List<Long> validRights) {
// Validate permissions for INSERT/UPDATE/DELETE
Long currentDeptId = userContext.getCurrentDepartmentId();
if (!validRights.contains(currentDeptId)) {
throw new PermissionDeniedException("No permission for this department");
}
}
@Override
public void validDs(List<Long> validRights, List<Long> withoutRights) {
// Whitelist support: if in whitelist, skip validation
Long currentDeptId = userContext.getCurrentDepartmentId();
if (withoutRights.contains(currentDeptId)) {
return;
}
validDs(validRights);
}
}@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
// Apply department-level data scope
public List<Employee> getEmployees() {
return DataScopeHelper
.strategy(DepartmentDataScopeStrategy.class)
.setSearchRight(getCurrentUserDeptIds())
.execute(() -> employeeMapper.selectAll());
}
// Apply with validation for update
public void updateEmployee(Employee employee) {
DataScopeHelper
.strategy(DepartmentDataScopeStrategy.class)
.setValidRights(employee.getDeptId())
.execute(() -> {
employeeMapper.updateById(employee);
return null;
});
}
// Complex scenario with whitelist
public void updateWithWhitelist(Employee employee) {
List<Long> adminDepts = getAdminDepartments();
DataScopeHelper
.strategy(DepartmentDataScopeStrategy.class)
.setValidRights(employee.getDeptId())
.setWithoutRights(adminDepts) // Admin depts bypass validation
.execute(() -> {
employeeMapper.updateById(employee);
return null;
});
}
}Before Data Scope:
SELECT * FROM employeeAfter Data Scope Applied:
SELECT * FROM employee t
LEFT JOIN department d ON t.dept_id = d.id
WHERE d.id IN (10, 20, 30)The library includes a powerful SQL printing feature for debugging:
# application-dev.yml (automatically enabled in non-prod environments)
spring:
profiles:
active: devConsole Output:
=== SQL Execution ===
Method: com.example.mapper.UserMapper.selectById
SQL: SELECT * FROM user WHERE id = 1 AND phone_number = '_sensitive_start_EnCrYpTeD123='
Time: 15 ms
Returned: 1 rows
Features:
- β Shows actual parameter values (not placeholders)
- β Displays execution time
- β Shows affected/returned row counts
- β Automatically disabled in production (profile containing "prod")
The library works out of the box with zero configuration. Default settings:
# These are implicit defaults, no need to configure
seven:
data-security:
encryption:
algorithm: DES # Default encryption algorithm
key: "key12345678" # Default key (change in production!)
sql-print:
enabled: true # Enabled in non-production environmentsspring:
profiles:
active: dev
# Custom encryption settings
seven:
data-security:
encryption:
enabled: true
algorithm: AES@Configuration
public class EncryptionConfig {
@Bean
@Primary // Make this the default algorithm
public EncryptionAlgo defaultEncryptionAlgo() {
return new MyCustomEncryptionAlgo();
}
}@Configuration
public class SqlPrintConfig {
@Bean
@ConditionalOnProperty(name = "sql.print.enabled", havingValue = "false")
public ConfigurationCustomizer disableSqlPrint() {
return configuration -> {
// SQL printing will not be configured
};
}
}@Data
public class Order {
@TableId
private Long id;
// Nested object encryption
private Customer customer;
@Data
public static class Customer {
@EncryptField
private String phone;
@EncryptField
private String email;
private Address address;
@Data
public static class Address {
@EncryptField
private String street;
private String city;
}
}
}@Data
public class Company {
@TableId
private Long id;
// List of encrypted fields
private List<Employee> employees;
@Data
public static class Employee {
@EncryptField
private String socialSecurityNumber;
private String name;
}
}// Combine multiple data scope strategies
public List<Document> getDocuments() {
return DataScopeHelper
.strategy(DepartmentDataScopeStrategy.class)
.setSearchRight(userDeptIds)
.execute(() ->
DataScopeHelper
.strategy(ProjectDataScopeStrategy.class)
.setSearchRight(userProjectIds)
.execute(() -> documentMapper.selectAll())
);
}The library automatically handles Encrypt type serialization:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// Encrypt type fields are automatically serialized as plain strings
return userService.getById(id);
}
}JSON Output:
{
"id": 1,
"username": "john",
"phoneNumber": "13800138000",
"email": "john@example.com"
}- EncryptionAlgo interface for pluggable encryption algorithms
- DataScopeStrategy interface for customizable access control rules
- AbstractEncryptAlgoContainer for algorithm instance creation and caching
- Lazy initialization with Spring bean integration
- AbstractEncryptAlgoContainer provides template for algorithm resolution
- Subclasses implement specific default algorithm logic
- DecryptProcessor and EncryptProcessor use Holder pattern for thread-safe singletons
- SqlPrint utility uses inner Holder class
- MyBatis Plugin mechanism for transparent encryption/decryption
- Chain of responsibility for multiple interceptors
- ObjectMapperEnhancer listens to
ContextRefreshedEventfor Jackson configuration
- Field metadata caching in
ClazzUtilreduces reflection overhead - Algorithm instance caching in containers
- Reflection Cache: Field metadata cached in
ConcurrentHashMap - Algorithm Cache: Encryption algorithm instances cached
- Table Metadata Cache: MyBatis-Plus table info cached
// β
Good: Use @EncryptField for automatic encryption
@EncryptField
private String phoneNumber;
// β Avoid: Manual encryption in business logic
private String phoneNumber;
public void setPhoneNumber(String phone) {
this.phoneNumber = encryptionService.encrypt(phone); // Not recommended
}
// β
Good: Batch operations are efficiently handled
userMapper.insertBatch(users); // Encryption happens in single intercept call
// β
Good: QueryWrapper parameters are optimized
new QueryWrapper<User>().in("phone_number", phoneList); // Batch encryption
// β οΈ Note: Disable encryption for non-sensitive fields
private String publicInfo; // No @EncryptField = No overhead- Encryption Overhead: ~1-2ms per field (DES algorithm)
- Reflection Overhead: Negligible after first access (cached)
- SQL Rewrite Overhead: < 1ms for data scope application
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserMapper userMapper;
@Test
void testEncryption() {
// Create user with sensitive data
User user = new User();
user.setUsername("test");
user.setPhoneNumber("13800138000");
// Save user (encryption happens automatically)
userService.createUser(user);
// Query directly from database to verify encryption
User dbUser = userMapper.selectById(user.getId());
// Phone number should be decrypted automatically
assertEquals("13800138000", dbUser.getPhoneNumber());
// Verify data is encrypted in database
String encryptedPhone = jdbcTemplate.queryForObject(
"SELECT phone_number FROM user WHERE id = ?",
String.class,
user.getId()
);
assertTrue(encryptedPhone.startsWith("_sensitive_start_"));
}
@Test
void testDataScope() {
// Set up data scope context
List<Employee> employees = DataScopeHelper
.strategy(DepartmentDataScopeStrategy.class)
.setSearchRight(Arrays.asList(10L, 20L))
.execute(() -> employeeMapper.selectAll());
// Verify only employees from departments 10 and 20 are returned
assertTrue(employees.stream()
.allMatch(e -> Arrays.asList(10L, 20L).contains(e.getDeptId())));
}
}Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit your changes:
git commit -am 'Add new feature' - Push to the branch:
git push origin feature/your-feature - Submit a pull request
# Clone repository
git clone https://github.com/qwzhang01/seven-data-security.git
cd seven-data-security
# Build project
mvn clean install
# Run tests
mvn testThis project is licensed under the MIT License - see the LICENSE file for details.
- Author: avinzhang
- Email: avinzhang@tencent.com
- GitHub: https://github.com/qwzhang01/seven-data-security
- Issues: Report Issues
- Thanks to the Spring Boot and MyBatis teams for their excellent frameworks
- Thanks to all contributors who have helped improve this library
Star β this repository if you find it helpful!