shelley shelley - 19 days ago 6
Java Question

Spring Data JPA Invalid page.sort Parameters

In a web application that uses Spring Data JPA with Hibernate, we utilize the web pagination functionality to provide paging and sorting capabilities in various lists of entities.

@Controller
public class MyEntityController {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView list(Pageable pageable) { ... }
}

@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(new PageableArgumentResolver());
}
}

public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, String> {
Page<MyEntity> findByPropertyX(String propertyX, Pageable pagable);
}


This allows for entity properties to be defined in the rendered html as special sort request parameters, where the
page.sort
value actually matches a property in the entity upon which to sort.

<table>
<thead>
<tr>
<th><a href="?page.sort=propertyX&amp;page.sort.dir=asc">Property X</a></th>
<th><a href="?page.sort=propertyY&amp;page.sort.dir=asc">Property Y</a></th>
</tr>
</thead>
<tbody>...</tbody>
</table>


This produces a resulting URL such as:

http://host/context-root/entities/?page.sort=propertyX&page.sort.dir=asc


The problem is that users may modify the URL to use invalid
page.sort
properties that either reference non-existent column/property names, or worse, that use invalid JPA query characters that result in invalid syntax.

For example, if the URL is modified to sort on "noSuchProperty":

http://host/context-root/entities/?page.sort=noSuchProperty&page.sort.dir=asc


But this property doesn't exist, the following exception will be thrown:

java.lang.IllegalArgumentException: No property noSuchProperty found for type class com.my.company.MyEntity
at org.springframework.data.repository.query.parser.Property.<init>(Property.java:76)
. . .
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:86)
. . .
at $Proxy68.findByPropertyX(Unknown Source)
at com.my.company.MyEntityRepository.findByPropertyX(MyEntityRepository.java:17


Likewise, if the URL is modified to an invalid query syntax character, such as """:

http://host/context-root/entities/?page.sort=%22&page.sort.dir=asc


The following error will occur:

java.lang.StackOverflowError
java.util.regex.Pattern$GroupTail.match(Pattern.java:4227)
. . .
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)


(There is also a third flavor of exceptions which results in a
org.hibernate.QueryException
when the
@Query
is explicitly defined on the Repository method.)

Spring Data JPA abstracts away the details of the sorting, paging, and handling of these parameters; however, it doesn't seem to gracefully handle these scenarios (i.e. where an invalid sort parameter is specified).

We could add in some additional custom logic to validate that the sort property actually exists on the entity; however, I'm wondering if there is a cleaner more centralized approach for doing this such that we don't lose the benefits and simplicity of the Spring Data JPA abstractions. We use this sorting capability throughout our app with many different entities, so ideally, we'd want more of a generic approach, rather than having to explicitly define or check the sort properties for every entity page requested.

Specifically, we actually extend the
PageableArgumentResolver
to accept an annotated sort default value that is provided in our controller (not illustrated in the code examples for simplicity), so we'd like to just fallback to this default sort order, or just the default sorting order for the entity, rather than throwing an exception.

Some ideas and attempts.. I could use a
QueryCreationListener
to intercept the query creation and get the sort parameter; however, I can't actually modify the query at that point. Or, I can extend and use a custom
PageableArgumentResolver
(we are already doing this) to grab the sort parameters; however, I don't have access to the entity at that point, nor the ability to determine if the entity actually has a property by that name. We could declare the supported properties explicitly; however, again, this defeats the idea of centrally and automatically handling this scenario without requiring specific or declared knowledge of the entities.

Is there any other type of interceptor or similar construct that I can utilize to centrally validate pageable sort parameters and modify if necessary before invoking the query? Or is there any type of configuration or way that Spring can automatically handle this scenario such that it more gracefully handles invalid sort params?

Answer

I was taking a look at the code and I think some more of the stack trace would be helpful. But from what I can see, I think there are two places you might want to tackle if you are in the mood to rewrite some Spring code.

There are two scenarios here, in the first one you are passing a sort field that doesn't exist in the object/table. What you really want is for that bad parameter to be silently ignored all the time, not just when passing in a 1PageableArgumentResolver]1. I'm thinking it should be an option on the AbstractQueryCreator (and hence, the JpaQueryCreator) to ignore bad parameters on a sort.

The second part that should be tackled is probably the PageableArgumentResolver. If you pass empty strings or something that doesn't make sense like %20 then it should ignore that parameter and not send it through to the PageRequest.

Happy hacking and good luck. Reading your post has made me realize that my site is vulnerable to the same problem and I really have no good solution.