一键导入
rpamis-security
// MyBatis-based enterprise data security component for data masking and encryption/decryption. Invoke when implementing data masking, database encryption, SM4 encryption, or working with @Masked/@SecurityField annotations.
// MyBatis-based enterprise data security component for data masking and encryption/decryption. Invoke when implementing data masking, database encryption, SM4 encryption, or working with @Masked/@SecurityField annotations.
| name | rpamis-security |
| description | MyBatis-based enterprise data security component for data masking and encryption/decryption. Invoke when implementing data masking, database encryption, SM4 encryption, or working with @Masked/@SecurityField annotations. |
Enterprise-grade MyBatis data security component providing annotation-based data masking and automatic database encryption/decryption with SM4 support.
Invoke this skill when you need to:
@Masked or @SecurityField annotationsFor JDK 17+ (Recommended):
<dependency>
<groupId>com.rpamis</groupId>
<artifactId>rpamis-security-spring-boot-starter</artifactId>
<version>1.1.4</version>
</dependency>
For JDK 8-17:
<dependency>
<groupId>com.rpamis</groupId>
<artifactId>rpamis-security-spring-boot-starter</artifactId>
<version>1.0.7</version>
</dependency>
application.yml (Complete Configuration):
rpamis:
security:
enable: true
ignore-decrypt-failed: true
desensitization-enable: true
custom-pointcut: '@within(org.springframework.web.bind.annotation.RestController)'
algorithm:
active: sm4
sm4:
key: 1234567890123456
prefix: ENC_SM4_
Configuration Parameters Explained:
| Parameter | Type | Default | Description |
|---|---|---|---|
enable | Boolean | false | Master switch for security component. When false, all features disabled |
ignore-decrypt-failed | Boolean | true | Return original value on decryption failure instead of throwing exception |
desensitization-enable | Boolean | true | Enable/disable masking AOP aspect |
custom-pointcut | String | "" | Custom AOP pointcut expression (e.g., RestController) |
algorithm.active | String | null | Active encryption algorithm (sm4) |
algorithm.sm4.key | String | null | SM4 encryption key (must be 16 characters) |
algorithm.sm4.prefix | String | ENC_SM4_ | Prefix to identify encrypted values |
For Data Masking:
import com.rpamis.security.annotation.Masked;
import com.rpamis.security.mask.MaskType;
public class UserVO {
@Masked(type = MaskType.NAME_MASK)
private String name;
@Masked(type = MaskType.PHONE_MASK)
private String phone;
@Masked(type = MaskType.IDCARD_MASK)
private String idCard;
}
For Nested Masking:
import com.rpamis.security.annotation.Masked;
import com.rpamis.security.annotation.NestedMasked;
import com.rpamis.security.mask.MaskType;
public class UserVO {
@Masked(type = MaskType.NAME_MASK)
private String name;
@NestedMasked
private ContactInfoVO contactInfo;
}
For Database Encryption:
import com.rpamis.security.annotation.SecurityField;
public class UserDO {
private Long id;
@SecurityField
private String password;
@SecurityField
private String idCard;
}
| MaskType | Input Example | Output Example | Use Case |
|---|---|---|---|
NO_MASK | 张三 | 张三 | No masking needed |
NAME_MASK | 张三 | 张* | User names |
PHONE_MASK | 13812345678 | 138****5678 | Phone numbers |
IDCARD_MASK | 110101199001011234 | 110101********1234 | ID cards |
EMAIL_MASK | example@domain.com | e****e@domain.com | Email addresses |
BANKCARD_MASK | 6222021234567890 | 6222****7890 | Bank cards |
ADDRESS_MASK | 北京市朝阳区 | 北京市*** | Addresses |
ALL_MASK | 任意内容 | ****** | Complete masking |
CUSTOM_MASK | ABCDEFG | AB###FG | Custom patterns |
public class UserVO {
@Masked(type = MaskType.CUSTOM_MASK, start = 2, end = 5, symbol = "#")
private String customField;
}
Result: ABCDEFG → AB###FG
Use @NestedMasked annotation to mark nested entity fields that need masking:
import com.rpamis.security.annotation.Masked;
import com.rpamis.security.annotation.NestedMasked;
import com.rpamis.security.mask.MaskType;
@Data
public class UserDetailVO implements Serializable {
private static final long serialVersionUID = 1L;
@Masked(type = MaskType.NAME_MASK)
private String name;
@NestedMasked
private ContactInfoVO contactInfo;
}
@Data
public class ContactInfoVO implements Serializable {
private static final long serialVersionUID = 1L;
@Masked(type = MaskType.PHONE_MASK)
private String phone;
@Masked(type = MaskType.EMAIL_MASK)
private String email;
@NestedMasked
private AddressVO address;
}
@Data
public class AddressVO implements Serializable {
private static final long serialVersionUID = 1L;
@Masked(type = MaskType.ADDRESS_MASK)
private String homeAddress;
@Masked(type = MaskType.ADDRESS_MASK)
private String officeAddress;
}
Controller Example:
@RestController
@RequestMapping("/api")
public class UserDetailController {
@PostMapping("/user/detail")
@Desensitizationed
public UserDetailVO getUserDetail() {
AddressVO address = new AddressVO();
address.setHomeAddress("北京市朝阳区建国路88号");
address.setOfficeAddress("北京市海淀区中关村");
ContactInfoVO contactInfo = new ContactInfoVO();
contactInfo.setPhone("13812345678");
contactInfo.setEmail("zhangsan@example.com");
contactInfo.setAddress(address);
UserDetailVO userDetail = new UserDetailVO();
userDetail.setName("张三");
userDetail.setContactInfo(contactInfo);
return userDetail;
}
}
Output:
{
"name": "张*",
"contactInfo": {
"phone": "138****5678",
"email": "z****n@example.com",
"address": {
"homeAddress": "北京市朝阳区***",
"officeAddress": "北京市海淀区***"
}
}
}
Key Points:
@NestedMasked on nested entity fields@NestedMasked fields (multi-level nesting)@Masked fields in nested entities will be processed automatically@Desensitizationed annotation on controller methodsAdd @Desensitizationed annotation to controller methods:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/info")
@Desensitizationed
public UserVO getUserInfo() {
return userService.getUser();
}
@GetMapping("/list")
@Desensitizationed
public List<UserVO> getUserList() {
return userService.getUserList();
}
}
Supported Return Types:
UserVOList<UserVO>Map<String, UserVO>Response<UserVO>Response@SecurityField annotated fields before saving@TableName("user")
public class UserDO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField(value = "name")
@SecurityField
private String name;
@TableField(value = "id_card")
@SecurityField
private String idCard;
@TableField(value = "phone")
@SecurityField
private String phone;
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void saveUser(UserDO user) {
userMapper.insert(user);
// user.name still equals original value (deep copy)
}
public UserDO getUser(Long id) {
return userMapper.selectById(id);
// name, idCard, phone are automatically decrypted
}
}
<mapper namespace="com.example.mapper.UserMapper">
<insert id="insert" parameterType="UserDO">
INSERT INTO user (name, id_card, phone)
VALUES (#{name}, #{idCard}, #{phone})
</insert>
<select id="selectById" resultType="UserDO">
SELECT id, name, id_card, phone
FROM user
WHERE id = #{id}
</select>
</mapper>
@PostMapping("/update")
public void updateUser() {
UserDO user = new UserDO();
user.setName("张三");
user.setIdCard("500101111118181952");
userMapper.insert(user);
// Update with same object reference
user.setName("李四");
userMapper.updateById(user);
UserDO result = userMapper.selectById(user.getId());
// result.name equals "李四"
}
@PostMapping("/update/wrapper")
public void updateWithWrapper() {
UpdateWrapper<UserDO> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda()
.set(UserDO::getName, securityAlgorithm.encrypt("李四"))
.eq(UserDO::getId, userId);
userMapper.update(null, updateWrapper);
}
@Component
public class CustomAlgorithmImpl implements SecurityAlgorithm {
@Override
public String encrypt(String content) {
// Your custom encryption logic
return encryptedContent;
}
@Override
public String decrypt(String content) {
// Your custom decryption logic
return decryptedContent;
}
}
@Component
public class CustomMaskFunction implements MaskFunction {
@Override
public String mask(String content) {
// Your custom masking logic
return maskedContent;
}
}
@Autowired
private SecurityUtils securityUtils;
public void checkEncryption(String value) {
boolean isEncrypted = securityUtils.checkHasBeenEncrypted(value);
// Returns true if value starts with ENC_SM4_
}
@SpringBootTest
public class SecurityTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsertAndDecrypt() {
UserDO user = new UserDO();
user.setName("张三");
user.setIdCard("500101111118181952");
user.setPhone("12345678965");
userMapper.insert(user);
UserDO result = userMapper.selectById(user.getId());
assertEquals("张三", result.getName());
assertEquals("500101111118181952", result.getIdCard());
assertEquals("12345678965", result.getPhone());
}
@Test
@Desensitizationed
public void testMasking() {
UserVO user = userService.getUser();
assertEquals("张*", user.getName());
assertEquals("500***********1952", user.getIdCard());
}
}
List<UserDO> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userService.saveBatch(userList);
Only encrypt sensitive fields:
public class UserDO {
private Long id;
private String username;
@SecurityField
private String password;
@SecurityField
private String idCard;
private Integer age;
}
The framework automatically isolates encryption/decryption cache to prevent interference:
@Test
public void testCacheIsolate() {
TestVersionV2DO encrypted = mapper.selectById(1);
TestVersionDO normal = new TestVersionDO();
normal.setName("张三");
mapper.insert(normal);
// normal.name is not affected by previous decryption
}
DO NOT hardcode encryption keys in source code:
rpamis:
security:
algorithm:
sm4:
key: ${SM4_KEY:default-key-for-dev}
Use environment variables or secret management services.
rpamis:
security:
ignore-decrypt-failed: true
Set to false in production to detect tampering attempts.
Symptoms: Data stored in plaintext
Checklist:
rpamis.security.enable is true@SecurityField annotation present on fieldDebug:
@Autowired
private SecurityAlgorithm securityAlgorithm;
String encrypted = securityAlgorithm.encrypt("test");
System.out.println(encrypted);
Symptoms: Sensitive data returned without masking
Checklist:
rpamis.security.desensitization-enable is true@Masked annotation present on field@Desensitizationed annotation on controller method@EnableAspectJAutoProxy)Debug:
@GetMapping("/test")
@Desensitizationed
public UserVO test() {
UserVO user = new UserVO();
user.setName("张三");
return user;
}
Symptoms: Encryption/decryption exceptions
Checklist:
Solution:
rpamis:
security:
algorithm:
sm4:
key: 1234567890123456
Symptoms: Encrypted data not decrypted
Possible Causes:
@SecurityField on insert)Solution:
rpamis:
security:
ignore-decrypt-failed: true
Symptoms: Nested entity fields not decrypted
Checklist:
@SecurityField annotationsExample:
public class OrderVO {
private UserDO user;
}
public class UserDO {
@SecurityField
private String idCard;
}
Breaking Changes:
ENC_SM4_)Migration Steps:
From MyBatis-Plus Encrypt:
@Encrypt with @SecurityFieldFrom ShardingSphere Encrypt:
@Entity
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
@SecurityField
private String password;
@SecurityField
private String idCard;
@SecurityField
private String phone;
@SecurityField
private String email;
private Integer status;
}
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/register")
public void register(@RequestBody SysUser user) {
userService.save(user);
}
@GetMapping("/info/{id}")
@Desensitizationed
public UserVO getUserInfo(@PathVariable Long id) {
SysUser user = userService.getById(id);
return convertToVO(user);
}
}
public class UserVO {
private Long id;
@Masked(type = MaskType.NAME_MASK)
private String name;
@Masked(type = MaskType.PHONE_MASK)
private String phone;
@Masked(type = MaskType.IDCARD_MASK)
private String idCard;
@Masked(type = MaskType.EMAIL_MASK)
private String email;
}
@TableName("orders")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
@SecurityField
private String customerPhone;
@SecurityField
private String customerAddress;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<OrderItem> items;
}
public class OrderItem {
private String productName;
@SecurityField
private String receiverPhone;
}
@GetMapping("/order/{id}")
@Desensitizationed
public OrderVO getOrder(@PathVariable Long id) {
Order order = orderService.getById(id);
return convertToVO(order);
}
rpamis-security/
├── rpamis-security-annotation/
│ ├── @Masked - Masking annotation
│ ├── @NestedMasked - Nested masking annotation
│ ├── @SecurityField - Encryption annotation
│ ├── @Desensitizationed - Controller masking trigger
│ └── MaskType - Masking type enum
├── rpamis-security-core/
│ ├── algorithm/ - Encryption algorithms (SM4)
│ ├── aop/ - Masking AOP aspect
│ ├── mybatis/ - MyBatis interceptors
│ ├── field/ - Field processors
│ └── factory/ - Mask function factory
├── rpamis-security-spring-boot-starter/
│ ├── SecurityAutoConfiguration
│ └── SecurityProperties
└── rpamis-security-test/
└── 130+ test scenarios (89%+ coverage)
| Feature | Rpamis-Security | Similar Projects |
|---|---|---|
| Any entity type masking | ✅ List, Map, non-generic | ❌ Single entity only |
| Nested masking | ✅ Multi-level nesting | ❌ Not supported |
| Auto encryption/decryption | ✅ Dynamic SQL support | ❌ Limited functionality |
| SM4 encryption | ✅ Supported | Partial support |
| Encryption failure handling | ✅ Return original value | ❌ Not supported |
| Deep copy design | ✅ Preserves source reference | ❌ Not supported |
| Test coverage | ✅ 89%+ / 130+ scenarios | ❌ None |
ignore-decrypt-failed: true in development for easier debuggingignore-decrypt-failed: false in production to detect data tampering@Desensitizationed on controller methods not service methods@Desensitizationed on controller methodsNeed help? Check the documentation or open an issue.