Data Masking is a common requirement in any new system. This 5-minute example will show how you can easily mask sensitive data in Java with mapstruct.
Why Mask Data
There are rules and regulations around what developers should be able to see when working with sensitive data. However, if you are to successfully support any system, you need that data available to scrutinize when you have problems.
The best way to achieve both is to mask the sensitive parts of any data, leaving enough of it available to support the system.
Get rid of too much and you won’t be able to work out what is happening.
Leave any sensitive data unmasked, and you’ll be in big trouble.
Mapstruct Makes Data Masking in Java easy
It’s generally a bit of a tedious job masking data with plenty of custom solutions based on other architectural components such as Splunk or log4j.
I think it’s much easier to use a MapStruct class to do any data hiding for you.
It takes a simple object and transforms it, masking data as it goes.
Understanding The Example
We’re going to take a simple customer details object.
{
"firstName": "John",
"middleName": "A",
"lastName": "Dobie",
"personalDetails": {
"niNumber": "NS1234567",
"dateOfBirth": "1970-01-01"
}
}
Were then going to use mapstruct to mask the data as below.
- Hide the middle name by setting it to null
- Set national insurance number to ********
- Set date of birth to ****-**-**
I’ve used a minimal example to show the technique.
I’ve used a sub-object to show how easy it is to mask hierarchical data.
Running The Example
The following commands will allow you to check out and run the example.
git clone https://github.com/johndobie/masking.git
mvn clean install spring-boot:run
{
"firstName": "John",
"middleName": "A",
"lastName": "Dobie",
"personalDetails": {
"niNumber": "NS1234567",
"dateOfBirth": "1970-01-01"
}
}
{
"firstName": "John",
"middleName": null,
"lastName": "Dobie",
"personalDetails": {
"niNumber": "********",
"dateOfBirth": "****-**-**"
}
}
Building The Example
PersonalDetails.java
A simple class containing some details which we will want to mask.
@Data
@Builder
public class PersonalDetails {
private String niNumber;
private String dateOfBirth;
}
PersonalDetails.javaCustomerDetails.java
A class containing more basic details and the personal details we want to mask.
@Data
@Builder
public class CustomerDetails {
private String firstName;
private String middleName;
private String lastName;
private PersonalDetails personalDetails;
}
Create A Mapstruct Transformer To Mask The Fields.
The mapstruct object is a simple interface which you define below.
The mappings tell mapstruct what to do with the fields. By default, they are just copied across.
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN)
public interface CustomerMaskTransformer {
public static final String DOB_DEFAULT = "****-**-**";
public static final String NI_DEFAULT = "********";
@Mapping(target="middleName", ignore = true)
@Mapping(target="personalDetails.niNumber", qualifiedByName = "niNumber")
@Mapping(target="personalDetails.dateOfBirth", qualifiedByName = "dateOfBirth")
CustomerDetails getMaskedCustomer(CustomerDetails customerDetails);
@Named("dateOfBirth")
default String maskDateOfBirth(String dob){
return DOB_DEFAULT;
}
@Named("niNumber")
default String maskNationalInsuranceNumber(String nationalInsuranceNumber){
return NI_DEFAULT;
}
}
Mapstruct will automatically generate an implementation class which will do the underlying transformations.
We have marked the ones we want to mask with a mapping. These mappings are explained in further detail below.
Removing Middle Name
The following line will make sure that the middle name is hidden.
Mapstruct will ignore it in the source object, so it will be null in the target one.
@Mapping(target="middleName", ignore = true)
Masking NI Number
The following lines will take the ni number and convert it to “********”
public static final String NI_DEFAULT = "********";
@Mapping(target="personalDetails.niNumber", qualifiedByName = "niNumber")
@Named("niNumber")
default String maskNationalInsuranceNumber(String nationalInsuranceNumber){
return NI_DEFAULT;
}
Masking Date of Birth
Finally, the following lines will set the date of birth to “****-**-**”
public static final String DOB_DEFAULT = "****-**-**";
@Mapping(target="personalDetails.dateOfBirth", qualifiedByName = "dateOfBirth")
@Named("dateOfBirth")
default String maskDateOfBirth(String dob){
return DOB_DEFAULT;
}
Testing The Transformer
We can use the transformer in a test to make sure the fields are being masked correctly.
The following line does the transformation – it couldn’t be any easier!
Customer maskedCustomer = customerMaskTransformer.getMaskedCustomer(customer);
We can use that in a test and make sure the masks are being applied.
@Test
void testPersonalDetailsAreCorrect() {
CustomerDetails customerDetails = getCustomerDetails();
CustomerDetails maskedCustomerDetails = customerMaskTransformer.getMaskedCustomer(customerDetails);
// Check the main fields are not modified
assertThat(maskedCustomerDetails.getFirstName()).isEqualTo(customerDetails.getFirstName());
assertThat(maskedCustomerDetails.getLastName()).isEqualTo(customerDetails.getLastName());
// Check the 3 fields are masked
assertThat(maskedCustomerDetails.getMiddleName()).isNull();
assertThat(maskedCustomerDetails.getPersonalDetails().getNiNumber()).isEqualTo(NI_DEFAULT);
assertThat(maskedCustomerDetails.getPersonalDetails().getDateOfBirth()).isEqualTo(DOB_DEFAULT);
}
Using it in a Microservice.
We can also create a simple Spring controller and demonstrate its use.
We can use one endpoint to return the original object and another to return the masked object.
@RestController
public class CustomerController {
static final CustomerMaskTransformer cmt =
mapppers.getMapper(CustomerMaskTransformer.class);
@GetMapping("/customer")
public CustomerDetails getCustomerEndpoint(){
return getCustomer();
}
@GetMapping("/customer/masked")
public CustomerDetails getMaskedCustomerEndpoint(){
return cmt.getMaskedCustomer(getCustomer());
}
private CustomerDetails getCustomer() {
PersonalDetails personalDetails = PersonalDetails.builder()
.niNumber("NS1234567")
.dateOfBirth("1970-01-01")
.build();
CustomerDetails customerDetails
= CustomerDetails.builder()
.personalDetails(personalDetails)
.firstName("John")
.middleName("A")
.lastName("Dobie")
.build();
return customerDetails;
}
}
Viewing The Results
You can point your browser at the following
http://localhost:8080/customer/masked
Summary.
This was a very simple example of using mapstruct to mask data in Java.
Hopefully, it shows how quick and easy it is to use this technique.
References
https://mapstruct.org/documentation/stable/reference/html/