Spring data rest - Is there a way to restrict the supported operations? - spring

I want to expose data from a database as Restful APIs in a Spring(SpringBoot) application. Spring Data Rest appears to be an exact fit for purpose for this activity.
This database is read-only for my application needs. The default provides all the HTTP methods. Is there a configuration that I can use to restrict (in fact prevent) the other methods from being exposed?

From the Spring docs on Hiding repository CRUD methods:
16.2.3. Hiding repository CRUD methods
If you don’t want to expose a save or delete method on your
CrudRepository, you can use the #RestResource(exported = false)
setting by overriding the method you want to turn off and placing the
annotation on the overriden version. For example, to prevent HTTP
users from invoking the delete methods of CrudRepository, override all
of them and add the annotation to the overriden methods.
#RepositoryRestResource(path = "people", rel = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
#Override
#RestResource(exported = false)
void delete(Long id);
#Override
#RestResource(exported = false)
void delete(Person entity);
}
It is important that you override both delete methods as the exporter
currently uses a somewhat naive algorithm for determing which CRUD
method to use in the interest of faster runtime performance. It’s not
currently possible to turn off the version of delete which takes an ID
but leave exported the version that takes an entity instance. For the
time being, you can either export the delete methods or not. If you
want turn them off, then just keep in mind you have to annotate both
versions with exported = false.

As of early 2018, there is now the ability to only expose repository methods explicitly declared for exposure (DATAREST-1176)
See RepositoryRestConfiguration
A Export false at Type level does not allow overriding with export true at Method level ticket (DATAREST-1034) was opened, but closed as a duplicate of DATAREST-1176. Oliver Gierke stated:
I'll resolve this as fixed against the version of DATAREST-1176 for
now but feel free to reopen in case there's anything else you need.
They are not exact duplicates and the functionality described in 1034 would have been more user friendly, but there are at least some options now.

By default, Spring boot exposes all methods to REST. You can set that to false.
config.setExposeRepositoryMethodsByDefault(false);
For more information, you can refer org.springframework.data.rest.core.config.RepositoryRestConfiguration.
Sample code snippet to do this:
#Configuration
public class ApplicationRepositoryConfig implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
..........
config.setExposeRepositoryMethodsByDefault(false);
}
}

Since Spring Data REST 3.1, we can configure exposure per HTTP method. I used the following snippet to disable exposure of PUT, PATCH, POST and DELETE methods for items and collections:
#Component
public class SpringDataRestCustomization implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
ExposureConfiguration exposureConfiguration = config.getExposureConfiguration();
exposureConfiguration.withItemExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.PUT)
.disable(HttpMethod.PATCH).disable(HttpMethod.POST).disable(HttpMethod.DELETE))
.withCollectionExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.PUT)
.disable(HttpMethod.PATCH).disable(HttpMethod.POST).disable(HttpMethod.DELETE));
}
}

Related

#Gateway is not required if we use #MessagingGateway

From Spring 4.0 onwards #MessagingGateway was introdued. Using it if we have only one gateway method in our Gateway interface , then we don't need to annotate the Gateway method with #Gateway.
Below is my example, where both are working.
So, my question is can we stop using #Gateway when we have only one method in Gateway interface?
Code-1:
#MessagingGateway(name="demoGateway")
public interface DemoGateway {
#Gateway(requestChannel = "gatewayRequestChannel",replyChannel = "nullChannel")
void accept(Message<String> request);
}
Code-2:
#MessagingGateway(name="demoGateway",defaultRequestChannel =
"gatewayRequestChannel",defaultReplyChannel = "nullChannel")
public interface DemoGateway {
void accept(Message<String> request);
}
Yes. You are right. You can do approach 2 and leave the single method that confirms to the default configuration of #MessagingGateway without annotation.
However in practice, I will only move the truly default values to the MessagingGateway and leave other values to #Gateway annotation.
This is because it makes life and readability easier in the future if you have to add more methods to DemoGateway in the future.

Spring boot rest ignore one class

I am developing a REST API using spring-boot-starter-data-rest. One class I want to sync with JPA is the User class containing information about users, including who is allowed to access the API.
Unfortunately, having the User and the UserRepository means that my User class is exposed in my API. I was able to remove things like the Id (in the configureRepositoryRestConfiguration function) and usernames and passwords (by adding #JsonIgnore to every variable of my User class).
Unfortunately, users of the API can still ask for the users table (who returns a list with empty users). Although this is not really a problem, I would rather remove the /users endpoint.
Adding #JsonIgnore to the whole User class is not possible.
Exporting repositories is depend on RepositoryDetectionStrategy. The default strategy is:
Exposes all public repository interfaces but considers #(Repository)RestResource’s exported flag.
According it to disable exporting of your 'repo' you can set exported flag to false for this repo:
#RepositoryRestResource(exported = false)
public interface UserRepo extends JpaRepository<User, Integer> {
//...
}
Another approach is to change globally the RepositoryDetectionStrategy to ANNOTATED:
Only repositories annotated with #(Repository)RestResource are exposed, unless their exported flag is set to false.
#Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
super.configureRepositoryRestConfiguration(config);
}
}
Then don't apply #RepositoryRestResource annotation to repos that doesn't need to be exported.
UPDATE
We can also use this application property to setup the strategy:
spring.data.rest.detection-strategy=default
Source
You can hide certain repositories by adding this annotation to your repository: #RepositoryRestResource(exported = false).
More informations here: http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repositories
There's such thing as projections.
You can define interface with fields you want and use it as repository's method:
#Projection(name = "simpleUser", types = { User.class })
interface SimpleUser {
String getFirstName();
String getLastName();
}

Spring Constraint Validation Context - Database Request Caching

I've written a custom Validation Annotation and a ConstraintValidator implementation, which uses a Spring Service (and executes a Database Query):
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, String> {
private final MyService service;
public MyValidator(MyService service) {
this.service = service;
}
#Override
public void initialize(MyValidationAnnotation constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return service.exists(value);
}
}
It's used like this:
public class MyEntity {
#Valid
List<Foo> list;
}
public class Foo {
#MyValidationAnnotation
String id;
}
This works quite nice, but service.exists(value) is getting called for every item within the list, which is correct, but could/should be optimized.
Question:
When validating an instance of MyEntity, I'd like to cache the results of the service.exists(value) calls.
I don't want to use a static HashMap<String, Boolean>, because this would cache the results for the entire application lifetime.
Is it possible to access some kind of Constraint Validation Context, which only exists while this particular validation is running, so I can put there the cached results?
Or do you have some other solution?
Thanks in advance!
You can use Spring's cache support. There might be other parts in the application which needs caching and this can be reused. And the setup is very simple too. And it will keep your code neat and readable.
You can cache your service calls. You need to put annotation on your service methods and a little bit of configuration.
And for cache provider you can use Ehcache. You have many options like setting ttl and max number of elements that can be cached and eviction policy etc etc if needed.
Or you can implement your own cache provider if your needs are simple. And if it is web request, In this cache you may find ThreadLocal to be useful. you can do all caching for this running thread using threadlocal. When the request is processed you can clear the threadlocal cache.

How does delete operation work with Rest in Spring Data

Currently we have exposed our methods like this
#RestController
#RequestMapping("/app/person")
public class PersonResource {
#Timed
public void delete(#PathVariable Long id) {
log.debug("REST request to delete Person: {}", id);
personRepository.delete(id);
}
}
The operations of this method, in terms of input and output, are very clear to the user developer.
This article http://spring.io/guides/gs/accessing-data-rest/ shows how to expose JPARepositories directly obviating the need of a service layer.
#RepositoryRestResource(collectionResourceRel="people", path="people")
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
}
It is not obvious to me how I can make a "delete operation" available with PathVariable Long id.
There is an excellent article on this topic. https://github.com/spring-projects/spring-data-rest/wiki/Configuring-the-REST-URL-path
But it actually shows how to supress export of a delete operation.
As documented here, Spring Data REST will expose item resources for the repository you declare. Thus, all you need to do is discover the URI of the resource to delete and issue a DELETE request to it.

Why does Spring allow controller annotated request mappings on private methods?

Just came accross this today in a Spring MVC cotnroller class,
#RequestMapping(value = { "/foo/*" }, method = { RequestMethod.GET})
private String doThing(final WebRequest request) {
...
return "jsp";
}
This is making it a bit harder to write a test, I'll probably change it to public but what's the point of allowing mappings on private methods?
Java does not provide a mechanism for limiting the target of annotations based on access modifier.
As #smp7d stated, Java does not limit the target of annotations based on access modifiers, but syntactically speaking, #RequestMapping should not work on private methods. Also we cannot limit this, since it would break the backward compatibility. So, you can either go for defining your methods as public or you can create your own custom implementation.
Take a look at this: Spring's #RequestMapping annotation works on private methods

Resources