问题
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
?
回答1:
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:
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 { }
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(); } }; } }
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()); } }
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"));
}
}
回答2:
HandlerMethodArgumentResolver would be the best in this regard.
- Create your annotation:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt {
String value();
}
- 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;
}
}
- 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());
}
}
- 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.
...
}
回答3:
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.
来源:https://stackoverflow.com/questions/46728972/custom-converter-for-requestparam-in-spring-mvc