Custom converter for @RequestParam in Spring MVC

后端 未结 3 859
攒了一身酷
攒了一身酷 2021-02-07 20:20

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 annotat

相关标签:
3条回答
  • 2021-02-07 21:00

    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"));
        }
    }
    
    0 讨论(0)
  • 2021-02-07 21:07

    HandlerMethodArgumentResolver would be the best in this regard.

    1. Create your annotation:

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Decrypt {
        String value();
    }
    
    1. Create your Custom HandlerMethodArgumentResolver:

    public class DecryptResolver implements HandlerMethodArgumentResolver {
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterAnnotation(Decrypt.class) != null;
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                WebDataBinderFactory binderFactory) throws Exception {
            Decrypt attr = parameter.getParameterAnnotation(Decrypt.class);
            String encrypted = webRequest.getParameter(attr.value());
            String decrypted = decrypt(encrypted);
    
            return decrypted;
        }
    
        private String decrypt(String encryptedString) {
            // Your decryption logic here
    
            return "decrypted - "+encryptedString;
        }
    }
    
    1. Register the resolver:

    @Configuration
    @EnableMvc // If you're not using Spring boot
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
              argumentResolvers.add(new DecryptResolver());
        }
    }
    
    1. Voila, you have your decrypted parameter. Note that you won't need to use @RequestParam anymore.

    @RequestMapping(value = "/customer", method = RequestMethod.GET)
    public String getAppointmentsForDay(@Decrypt("secret") String customerSecret) {
    System.out.println(customerSecret);  // Needs to be a decrypted value.
       ...
    }
    
    0 讨论(0)
  • 2021-02-07 21:11

    You can try by adding a CharacterEncodingFilter with init-param encoding UTF-8 in web.xml file. Check out this example.

    However If it still doesn't work, you can force encoding by adding the below param along with above init-param.

    <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
    </init-param>
    

    Let me know If It works for you.

    0 讨论(0)
提交回复
热议问题