[Spring] Obfuscate specific fields using annotations [Java]

If you use the ID automatically numbered in the database as it is for the ID displayed on the screen, the user will guess various things. (It is called the number of users)

For such a special field, I think that it is common to prepare a dedicated value object and write the process of ID conversion internally. However, when the type of the request or response model cannot be changed due to complicated circumstances, I investigated whether it can be managed by annotation.

The working environment is Java9,SpringBoot (2.0.0.RELEASE). (I haven't done anything special, so I think other versions are fine)

Requirements

The requirements for this time are as follows.

--The controller request model and response model types cannot be changed. --However, annotations can be added --I want to be able to handle both the client and the server without worrying about obfuscation. --Obfuscation is conversion from Long to Long

specification

Based on the above requirements, I would like to make the specifications as follows.

--Annotation is ʻObfuscate` --Only fields are targeted --This time, add 1 for obfuscation and subtract 1 for simplification (it should be more complicated)

Implementation

Serializer

This class is obfuscated. (Where to leave the server) Inherit and implement StdSerializer. If it is null, it will end as it is. (Because Not Null etc. should be expressed by other annotations)

ObfuscateSerializer.java


public class ObfuscateSerializer extends StdSerializer<Long> {

    private static final long serialVersionUID = -6910413385029615313L;

    protected ObfuscateSerializer() {
        super(Long.class);
    }

    @Override
    public void serialize(Long value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        //Obfuscation logic
        if (value == null) {
            return;
        }
        gen.writeNumber(value + 1);
    }
    
}

Deserializer

This is a class that simplifies. (Where entering the server) Like the serializer, use StdDeserializer.

ObfuscateDeserializer.java


public class ObfuscateDeserializer extends StdDeserializer<Long> {

    private static final long serialVersionUID = 4303388430635458159L;

    protected ObfuscateDeserializer() {
        super(Long.class);
    }

    @Override
    public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        //Simplified logic
        return Long.parseLong(p.getValueAsString()) - 1;
    }

}
{
"timestamp": 1521424385168,
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: For input string: \"hoge\"; nested exception is com.fasterxml.jackson.databind.JsonMappingException: For input string: \"hoge\" (through reference chain: com.example.Hoge[\"value\"])",
"path": "/api/sample/obfuscate"
}

Annotation

Since it is a marker annotation, it does not have any properties. The target is set only to ʻElementType.FIELD` from the specifications.

Obfuscate.java


@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface Obfuscate { 
}

Annotation interceptor

It is a class that associates the defined annotation with the serializer / deserializer. Inherit and implement JacksonAnnotationIntrospector. This class is called for each item when Jackson in Spring Boot converts json to the specified class and the target class, field, or method is annotated. This time we'll make the serializer and deserializer unique, so we'll override the findSerializer and findDeserializer.

MyAnnotationInterceptor.java


public class MyAnnotationInterceptor extends JacksonAnnotationIntrospector {

    private static final long serialVersionUID = -7722925738908936096L;

    @Override
    public Object findSerializer(Annotated a) {
        if (a.hasAnnotation(Obfuscate.class)) {
            return ObfuscateSerializer.class;
        }
        return super.findSerializer(a);
    }

    @Override
    public Object findDeserializer(Annotated a) {
        if (a.hasAnnotation(Obfuscate.class)) {
            return ObfuscateDeserializer.class;
        }
        return super.findDeserializer(a);
    }

}

Config

Register the above annotation interceptor with Spring Boot.

JacksonConfig.java


@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.annotationIntrospector(new MyAnnotationInterceptor());
        return builder;
    }

}

Example of use

Now that you're ready, you can add @Obfuscate to the request or response model fields and they will be converted automatically.

ObfuscateRequest.java


@lombok.Value
public class ObfuscateRequest {
    
    @Obfuscate
    Long id;

}

Summary

This time, I defined annotations to obfuscate and simplify the fields. I think that there are situations where it is useful when it has already been released and it is difficult to change the class, or when the model is reused and it can not be changed. Also, in the case of value objects, there are some inconveniences (although it can be avoided) such as deepening the hierarchy when using json, so I thought that it was a good choice.

reference

https://qiita.com/nijuya_o/items/0f512792c4db27913c7a https://qiita.com/k_ui/items/b6e1763d023da96ea46f

Recommended Posts

[Spring] Obfuscate specific fields using annotations [Java]
Using Mapper with Java (Spring)
Spring Java
About Spring Dependency Injection using Java, Kotlin
[Java] Spring DI ③
Create a portfolio app using Java and Spring Boot
Sorting using java comparator
Scraping practice using Java ②
Scraping practice using Java ①
Review Java annotations now
Try using Spring JDBC