Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom converter for @RequestParam in Spring MVC

I am getting an encrypted String as Query parameter to a Spring rest controller method.

I wanted to decrypt the string before it reaches the method based on some annotation (say @Decrypt) like below

@RequestMapping(value = "/customer", method = RequestMethod.GET)
public String getAppointmentsForDay(@RequestParam("secret") @Decrypt String customerSecret) {
    System.out.println(customerSecret);  // Needs to be a decrypted value.
   ...
}

Is a custom Formatter the right approach in this use case?

Or should I use a custom HandlerMethodArgumentResolver?

like image 373
Prabhakar D Avatar asked Oct 13 '17 11:10

Prabhakar D


People also ask

How do I register a custom converter in Spring?

Creating a Custom Type Converter in Spring MVC To create a custom type converter, we need to implement the Converter<S,T> interface. While implementing this interface we need to pass following 2 parameters to the method. S – The source object type.

What is the use of @RequestParam in Spring MVC?

In Spring MVC, the @RequestParam annotation is used to read the form data and bind it automatically to the parameter present in the provided method. So, it ignores the requirement of HttpServletRequest object to read the provided data.

Does Spring MVC customizable data binding?

Spring MVC provides one more annotation, @ModelAttributes , for binding data to the Command object. It is another way to bind the data and to customize the data binding. This annotation allows you to control the creation of the Command object.

What is @RequestParam annotation in Spring MVC?

@RequestParam is a Spring annotation used to bind a web request parameter to a method parameter. It has the following optional elements: defaultValue - used as a fallback when the request parameter is not provided or has an empty value. name - name of the request parameter to bind to.


1 Answers

A custom implementation of org.springframework.format.Formatter is a valid approach for this use case. This is how Spring itself implements formatters for dates, currencies, number styles etc.

Steps:

  1. Declare an annotation: Decrypt:

    import java.lang.annotation.*;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
    public @interface Decrypt {
    
    }
    
  2. Declare an AnnotationFormatterFactory which uses the new annotation:

    import org.springframework.context.support.EmbeddedValueResolutionSupport;
    import org.springframework.format.AnnotationFormatterFactory;
    import org.springframework.format.Formatter;
    import org.springframework.format.Parser;
    import org.springframework.format.Printer;
    
    import java.text.ParseException;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Locale;
    import java.util.Set;
    
    public class DecryptAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
            implements AnnotationFormatterFactory<Decrypt> {
    
        @Override
        public Set<Class<?>> getFieldTypes() {
            Set<Class<?>> fieldTypes = new HashSet<>();
            fieldTypes.add(String.class);
            return Collections.unmodifiableSet(fieldTypes);
        }
    
        @Override
        public Printer<String> getPrinter(Decrypt annotation, Class<?> fieldType) {
            return configureFormatterFrom(annotation);
        }
    
        @Override
        public Parser<String> getParser(Decrypt annotation, Class<?> fieldType) {
            return configureFormatterFrom(annotation);
        }
    
        private Formatter<String> configureFormatterFrom(Decrypt annotation) {
            // you could model something on the Decrypt annotation for use in the decryption call
            // in this example the 'decryption' call is stubbed, it just reverses the given String
            // presumaby you implementaion of this Formatter will be different e.g. it will invoke your encryption routine
            return new Formatter<String>() {
                @Override
                public String print(String object, Locale locale) {
                    return object;
                }
    
                @Override
                public String parse(String text, Locale locale) throws ParseException {
                    return new StringBuilder(text).reverse().toString();
                }
            };
        }
    }
    
  3. Register this formatter factory with your web context:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @Configuration
    public class WebConfigurer extends WebMvcConfigurerAdapter {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            super.addFormatters(registry);
            registry.addFormatterForFieldAnnotation(new DecryptAnnotationFormatterFactory());
        }
    }
    
  4. That's it.

With the above in place, all usages of a @RequestParam which are qualified with @Decrypt will be passed through the parse() method declared in DecryptAnnotationFormatterFactory so you can implement your decryption call there.

To prove this, the following test passes:

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = YourController.class)
public class YourControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void theSecretRequestParameterWillBeConverted() throws Exception {
        MvcResult mvcResult = mockMvc.perform(get("/customer?secret=abcdef")).andExpect(status().isOk()).andReturn();

        // the current implementation of the 'custom' endpoint returns the value if the secret request parameter and
        // the current decrypt implementation just reverses the given value ...
        assertThat(mvcResult.getResponse().getContentAsString(), is("fedcba"));
    }
}
like image 180
glytching Avatar answered Sep 27 '22 21:09

glytching