3

I have JSON request and response, I want to print the JSONs in the log, but there are some secured fields which I want to avoid to print in the log, I am trying to mask fields keys: example:

before masking:

  {"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}

after masking

{"username":"user1","password":"XXXXXX","country":"US","creditCardNumber":"XXXXXX"}

I am using java Gson lib, please help me to do that

EDIT

I want to pass the keys dynamically, so in function a I want to mask these fields, but in function b different fields.

jdev
  • 161
  • 3
  • 6
  • 14
  • See this: http://stackoverflow.com/questions/9063558/java-gson-replace-password-value-while-serialization – habsq Jul 06 '15 at 09:06
  • http://stackoverflow.com/questions/15159610/update-elements-in-a-jsonobject – sinclair Jul 06 '15 at 09:08
  • I checked that question, but I want the masking to be dynamic, for any Json, just select key and mask value – jdev Jul 06 '15 at 09:09

2 Answers2

5

I think you should exclude that fields from log. Below is a simple example using Gson and @Expose annotation.

public static void main(String[] args) throws IOException {
    String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";

    Gson gson = new Gson();
    User user = gson.fromJson(json, User.class);

    System.out.println(gson.toJson(user));

    Gson gsonExpose = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    System.out.println(gsonExpose.toJson(user));
}

public class User {
    @Expose
    private String username;
    private String password;
    @Expose
    private String country;
    private String creditCardNumber;
}

Output will be:

{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
{"username":"user1","country":"US"}

Another solution using Reflection:

public static void main(String[] args) throws IOException {
    String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";

    Gson gson = new Gson();
    User user = gson.fromJson(json, User.class);

    List<String> fieldNames = Arrays.asList("password", "creditCardNumber");
    System.out.println(mask(user, fieldNames, "XXXXXXX"));
}

public static String mask(Object object, List<String> fieldNames, String mask) {
    Field[] fields = object.getClass().getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
        if (fieldNames.contains(fields[i].getName())) {
            try {
                fields[i].setAccessible(true);
                if (fields[i].get(object) != null) {
                    fields[i].set(object, mask);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    Gson gson = new Gson();

    return gson.toJson(object);
}
codeaholicguy
  • 1,671
  • 1
  • 11
  • 18
  • is this code will exclude fields from serialisation and deserialisation ? – jdev Jul 06 '15 at 09:23
  • When you want to exclude field you can use GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(). When you serialize all the fields without @Expose will be excluded and the same with deserialize. If you use new Gson(), every thing is normal. – codeaholicguy Jul 06 '15 at 09:25
  • Masking will be happened always, can I pass the keys which I want to hide/mask, not using annotation? – jdev Jul 06 '15 at 09:31
  • No, if you want to pass exactly what key to exclude or mask, you should use reflection to do that, I updated answer with another solution using reflection. – codeaholicguy Jul 06 '15 at 09:40
  • @jdev I updated the answer, function mask with parameters are object you want to mask, list field name you want to mask, and the string you want to become the mask, check it and tell me if any. – codeaholicguy Jul 06 '15 at 09:43
  • but the real value of field is changed after masking – jdev Jul 07 '15 at 10:40
0

I like the above solution to mask using reflection but wanted to extend same for other field types and saving masked field to unmask again.

Create annotation @MaskedField on top of field.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MaskedField {
}
public <T> Map<String,? super Object>  maskObjectFields(T object){
    Map<String,? super Object> values = new HashMap<>();
    Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
            forEach(annotatedField->{
                try {
                    if(annotatedField.getType().isAssignableFrom(String.class)) {
                        annotatedField.setAccessible(true);
                        values.put(annotatedField.getName(),annotatedField.get(object));
                        annotatedField.set(object, maskString((String) annotatedField.get(object)));
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
    return values;
}


public <T> void unMaskObjectFields(T object,Map values){
    Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
            forEach(annotatedField->{
                try {
                    annotatedField.setAccessible(true);
                    annotatedField.set(object,values.get(annotatedField.getName()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
}

private String maskString(String value){
    if(Objects.isNull(value)) return null;
    return null; //TODO: your logic goes here for masking
}
Laurel
  • 5,965
  • 14
  • 31
  • 57