Spring MVC Missing matrix variable - java

I'm trying to add a matrix parameter (or matrix variable) to my Rest Controller using SpringMVC (from Spring boot 1.2.3.RELEASE)
Here is my code :
#RestController
public class SubAgentsController {
#RequestMapping(value = "/{subagents}", method = RequestMethod.GET)
public SubAgent subAgents(#MatrixVariable(value="agentName", pathVar="subagents") String agentName) {
System.out.println(agentName);
}
}
Unfortunately, when I try to get :
http://localhost:8080/subagents;agentName=hello
that is the answer I receive :
There was an unexpected error (type=Bad Request, status=400).
Missing matrix variable 'agentName' for method parameter of type String
What did I do wrong ? According to http://docs.spring.io/spring-framework/docs/3.2.0.M2/reference/html/mvc.html that should work :-(
Thanks for your answers!

In SpringBoot Application In order to enable Matrix variables you need to define below override code
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
Otherwise, they’re disabled by default

As the documentation you linked to states,
Note that to enable the use of matrix variables, you must set the
removeSemicolonContent property of RequestMappingHandlerMapping to
false. By default it is set to true with the exception of the MVC
namespace and the MVC Java config both of which automatically enable
the use of matrix variables.
If you're configuring your application by extending WebMvcConfigurationSupport, then override the requestMappingHandlerMapping method which prepares the RequestMappingHandlerMapping and set its appropriate property.
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
final RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
requestMappingHandlerMapping.setRemoveSemicolonContent(false); // <<< this
return requestMappingHandlerMapping;
}
You'll then be all set.
With Spring Boot, I think all you need is to declare a #Bean method with the above, ie. that returns a RequestMappingHandlerMapping instance.

If you are using Spring Data and its controller mapping, try something like this also
#Configuration
public class DataMvcConfig extends RepositoryRestMvcConfiguration {
#Override
public DelegatingHandlerMapping restHandlerMapping() {
final DelegatingHandlerMapping delegatingHandlerMapping = super.restHandlerMapping();
for (HandlerMapping delegate : delegatingHandlerMapping.getDelegates()) {
if (delegate instanceof AbstractHandlerMapping) {
((AbstractHandlerMapping)delegate).setRemoveSemicolonContent(false);
}
}
return delegatingHandlerMapping;
}
}

Related

Is using #CrossOrigin the same as overriding addCorsMapping() in Spring?

In my controller I currently added the following annotation #CrossOrigin:
#RestController
#RequestMapping(value = "/dev/test")
#CrossOrigin
public class MyController {
...
}
And also wondering the following implementation in WebConfig:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
private String allowedRequest = "/**";
private String allowedOrigins = "*";
private String[] allowedMethods = {"GET", "POST", "DELETE", "OPTIONS"};
#Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping(allowedRequest).allowedOrigins(allowedOrigins)
.allowedMethods(allowedMethods);
}
}
Are those two options provide the same result? And are there any difference from security standpoint (which one is more secure than the other)?
Thank you!
WebMvcConfigurer#addCorsMappings(CorsRegistry) creates a global CORS configuration applied to all controllers, and #CrossOrigin allows for a more fine-grained control over it. For the case when they are used together, as stated in the javadoc of #CrossOrigin:
The rules for combining global and local configuration are generally additive -- e.g. all global and all local origins. For those attributes where only a single value can be accepted such as allowCredentials and maxAge, the local overrides the global value.

ControllerAdvice #InitBinder setDisallowedFields doesn't work

A JAVA project has been built with spring boot version 2.5.3.
Due to the “Spring4shell” (CVE-2022-22965) security risk, we have to take mitigation actions.
It's not possible to upgrade the Spring Boot version since several other dependencies are incompatible with the latest Spring Boot version.
So, it has been decided to apply a suggested workaround according to https://www.springcloud.io/post/2022-03/spring-framework-rce-early-announcement/#gsc.tab=0
According to the guide, there are 2 workarounds suggested.
Setting disallowedFields on WebDataBinder through an #ControllerAdvice method
#ControllerAdvice
#Order(Ordered.LOWEST_PRECEDENCE)
public class BinderControllerAdvice {
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(denylist);
}
}
Extend RequestMappingHandlerAdapter to update the WebDataBinder:
#Bean
public WebMvcRegistrations mvcRegistrations() {
return new WebMvcRegistrations() {
#Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new ExtendedRequestMappingHandlerAdapter();
}
};
}
private static class ExtendedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
#Override
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> methods) {
return new ServletRequestDataBinderFactory(methods, getWebBindingInitializer()) {
#Override
protected ServletRequestDataBinder createBinderInstance(
Object target, String name, NativeWebRequest request) throws Exception {
ServletRequestDataBinder binder = super.createBinderInstance(target, name, request);
String[] fields = binder.getDisallowedFields();
List<String> fieldList = new ArrayList<>(fields != null ? Arrays.asList(fields) : Collections.emptyList());
fieldList.addAll(Arrays.asList("class.*", "Class.*", "*.class.*", "*.Class.*"));
binder.setDisallowedFields(fieldList.toArray(new String[] {}));
return binder;
}
};
}
}
Both of the approaches have been tried but none of them could reject the following request.
HOST:PORT/path?class.module.classLoader.URLs%5B0%5D=0
What can be the missing point of our approach?
Request will not be rejected by setting values to webDataBinder.setDisallowedFields().
WebDataBinder provides two methods setAllowedFields and setDisallowedFields to set the list of attribute names which can be and cannot be used in data binding process for model objects.
This validation will happen in org.springframework.validation.DataBinder::doBind method. inside the doBind method disallowed fields will be removed from the field list

How to call #PreDestroy for a bean declared with #Bean annotation

I have a bean declared with annotation #Bean
#Bean
public Set<DefaultMessageListenerContainer> beans() {
Set<DefaultMessageListenerContainer> containerSet = new HashSet<DefaultMessageListenerContainer>();
return containerSet;
}
I have some operations to be performed when I am destroying the bean. How can I achieve that?
I know I can use #predestroy annotation on a method in a class annotated with #Component but not sure how can I do that when declared #Bean annotation.
EDIT :
#Bean(destroyMethod="stopContainers")
public Set<DefaultMessageListenerContainer> containers() {
Set<DefaultMessageListenerContainer> containerSet = new HashSet<DefaultMessageListenerContainer>();
return containerSet;
}
public void stopContainers(){
Set<DefaultMessageListenerContainer> containerSet = containers();
......
}
}
But I am getting an error , Couldn't find a destroy method named 'stopContainers' on bean with name 'containers'
How to fix this?
Expanded from other comment - here's an example to wrap:
#Bean(destroyMethod="stopContainers")
public StoppableSetWrapper<DefaultMessageListenerContainer> containers() {
StoppableSetWrapper<DefaultMessageListenerContainer> wrapper = new StoppableSetWrapper<>();
return wrapper;
}
public class StoppableSetWrapper<T> {
private final Set<T> containers = new HashSet<T>();
public boolean add(T container) {
return containers.add(container);
}
// other Set related methods as needed...
public void stopContainers() {
// clean up...
}
}
The code which uses the injected/autowired bean will need to be updated since the bean type has changed.
Generally you can specify destroyMethod parameter for the #Bean annotation. And define the particular implementation for this method in your bean class.
As you're using Set you have no chance to add destroyMethod into the Set.class. So you have to wrap it (as Andrew proposed).
Actually, I don't like this kind of approach at all. It seems more preferable not to use Set of beans and find another workaround (by destroying them one by one). In my opinion, you can implement a separate manager class performing operations on your containers.

How to configure a default #RestController URI prefix for all controllers?

I know you can set the server.contextPath in application.properties to change the root context.
Also, I can add an additional context in the application config for Spring Boot like the following example (in Groovy) to add an "/api" to the URL mappings of the root context:
#Bean
ServletRegistrationBean dispatcherServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
reg.name = "dispatcherServlet"
reg.addInitParameter("contextConfigLocation", "")
reg.addUrlMappings("/api/*")
reg.loadOnStartup = 2
reg
}
}
I am trying to have a separate base URI "/api" specifically for web service calls, that I can leverage for security, etc. However using the above approach will mean that any of my URIs, web service or not, can be reached with "/" or "/api", and provides no concrete segregation.
Is anyone aware of a better approach to set a base path for all #RestController(s) using configuration, without having to formally prefix every controller with /api/? If I am forced to manually prefix the URI for each controller, it would be possible to mistakenly omit that and bypass my security measures specific to web services.
Here is a reference in Stack Overflow to the same type of question, which was never completely answered:
Spring Boot: Configure a url prefix for RestControllers
In continuation to the currently accepted solution the github issue addresses the same.
Spring 5.1 and above you can implement WebMvcConfigurer and override configurePathMatch method like below
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Now all the #RestControllers will have /api as the prefix path alongside the path configured.
Official Documentation
There's a new solution to solve this kind of problem available since Spring Boot 1.4.0.RC1 (Details see https://github.com/spring-projects/spring-boot/issues/5004)
The solution of Shahin ASkari disables parts of the Auto configuration, so might cause other problems.
The following solution takes his idea and integrates it properly into spring boot. For my case I wanted all RestControllers with the base path api, but still serve static content with the root path (f.e. angular webapp)
Edit: I summed it up in a blog post with a slightly improved version see https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/
#Configuration
public class WebConfig {
#Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
private final static String API_BASE_PATH = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
RestController restApiController = beanType.getAnnotation(RestController.class);
if (restApiController != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
};
}
};
}
}
Also You can achieve the same result by configuring WebMVC like this:
#Configuration
public class PluginConfig implements WebMvcConfigurer {
public static final String PREFIX = "/myprefix";
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}
}
Implement WebMvcConfigurer on any #Configuration class.
Override configurePathMatch method.
You can do many useful things with PathMatchConfigurer e.g. add prefix for several classes, that satisfy predicate conditions.
I had the same concern and was not a fan of the Spring EL option due to the issues documented and I wanted the prefix to be tightly controlled in the controllers but I did not want to depend on the developers doing the right thing.
There might be a better way these days but this is what I did. Can you guys see any downsides, I am still in the process of testing any side-effects.
Define a custom annotation.
This allows a developer to explicitly provide typed attributes such as int apiVersion(), String resourceName(). These values would be the basis of the prefix later.
Annotated rest controllers with this new annotation
Implemented a custom RequestMappingHandlerMapping
In the RequestMappingHandlerMapping, I could read the attribute of the custom annotation and modify the final RequestMappingInfo as I needed. Here are a few code snippets:
#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyCustomRequestMappingHandlerMapping();
}
}
And in the MyCustomRequestMappingHandlerMapping, overwrite the registerHandlerMethod:
private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
public MyCustomRequestMappingHandlerMapping() {
super();
}
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
// find the class declaring this method
Class<?> beanType = method.getDeclaringClass();
// check for the My rest controller annotation
MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
if (myRestAnnotation != null) {
// this is a My annotated rest service, lets modify the URL mapping
PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
// create a pattern such as /api/v${apiVersion}/${resourceName}
String urlPattern = String.format("/api/v%d/%s",
myRestAnnotation.apiVersion(),
myRestAnnotation.resourceName());
// create a new condition
PatternsRequestCondition apiPattern =
new PatternsRequestCondition(urlPattern);
// ask our condition to be the core, but import all settinsg from the old
// pattern
PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}",
beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
mapping = new RequestMappingInfo(
mapping.getName(),
updatedFinalPattern,
mapping.getMethodsCondition(),
mapping.getParamsCondition(),
mapping.getHeadersCondition(),
mapping.getConsumesCondition(),
mapping.getProducesCondition(),
mapping.getCustomCondition()
);
}
super.registerHandlerMethod(handler, method, mapping);
}
}
Slightly less verbose solution which doesn't duplicate the logic of checking the annotation, but only changes the mapping path:
private static final String API_PREFIX = "api";
#Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
if (mappingForMethod != null) {
return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
} else {
return null;
}
}
};
}
};
}
Side effects
Your error controller will also be mapped under /api/error, which breaks error handling (DispatcherServlet will still redirect errors to /error without prefix!).
Possible solution is to skip /error path when adding /api prefix in the code above (one more "if").
Someone has filed an issue in the Spring MVC Jira and come up with a nice solution, which I am now using. The idea is to use the Spring Expression Language in the prefix placed in each RestController file and to refer to a single property in the Spring Boot application.properties file.
Here is the link of the issue: https://jira.spring.io/browse/SPR-13882

Spring MVC custom converter not working

I am trying to create a custom Converter for my Spring MVC application but it is never triggered.
Here's how I implemented it and registered it :
public class ObjectIdSpringConverter implements Converter<String, ObjectId>{
public ObjectIdSpringConverter(){
System.out.println("ObjectIdSpringConverter picked up ");
}
#Override
public ObjectId convert(String source) {
System.out.println("ObjectIdSpringConverter converting");
return new ObjectId(source);
}
}
And in my Spring MVC configuration I register it as follow :
#Configuration
#ComponentScan(basePackages = {"myapp.web.controller"})
public class SpringMvcConf extends WebMvcConfigurationSupport{
#Override
protected void addFormatters(FormatterRegistry registry) {
registry.addConverter(new ObjectIdSpringConverter());
}
//other stuff here
}
I placed some breakpoints in the converter's constructor and convert() method, and I can see that it is properly constructed, however the convert() method is never called which results in icorrect values received by my controller.
Here's an example controller method :
#Controller
public class HomeController {
#RequestMapping(value="/home/testObjectId.amlgm", method = RequestMethod.GET)
public ObjectId testObjectId(ObjectId oid){
System.out.println("expected 552952cec9ac88712ee0d36b, actual " + oid.toString());
System.out.println(oid);
return oid;
}
}
I know the convert method is never called because the break point is never hit and because the value is not the expected one. I also debugged / traced what spring was doing and it does not seem to use my converter to instantiate the parameter ObjectId of the controller method.
Can anyone point me to what I am missing ?
Thanks
Your formatter should implement Formatter<T>, where T will be ObjectId in your case
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/format/FormatterRegistry.html#addFormatter-org.springframework.format.Formatter-

Categories