Search code examples
javaspringspring-bootspring-securitydto

Security questions on Spring Data JPA Rest (JpaRepository)


I'm (trying to :) using spring-boot-starter-data-rest in my spring boot app to quickly serve the model through true, fullblown, restFULL api. That works great.

Question 1 (Security):

The advantage of Spring JpaRepository is I don't need to code basic functions (save, findAll, etc). Is it possible to secure these auto-implemented methods without overriding all of them (wasting what Spring provided for me)? i.e.:

public interface BookRepository extends JpaRepository<Book, Long> {

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    <S extends Book> Book save(Book book);
}

 .

Question 2 (Security):

How to secure a JpaRepository to prevent updating items the loggeg-in user is not an owner? i.e.: User is allowed to modify only his/her own properties. i.e.2: User is allowed to modify/delete only the Posts he/she created. Sample code is highly welcome here.

 .

Question 3 (DTOs):

Some time ago I had an argue with a developer friend: He ensisted that there MUST be DTOs returned from Spring MVC controllers. Even if the DTO is 1-1 copy of the model object. Then I reserched, asked other guys and confirmed it: DTOs are required to divide/segregate the application layers.

How this relates to JpaRepositories? How to use DTOs with Spring auto serverd rest repos? Should I DTOs at all?

Thanks for your hints/answers in advance !


Solution

  • Question 1: Security

    Some old docs mention:

    [...] you expose a pre-defined set of operations to clients that are not under you control, it’s pretty much all or nothing until now. There’s seemingly no way to only expose read operations while hiding state changing operations entirely.

    which implies that all methods are automatically inherited (also, as per standard java inheritance behavior).

    As per the @PreAuhtorize docs, you can place the annotation also on a class / interface declaration.

    So you could just have one basic interface extend JpaRepository

    @NoRepositoryBean // tell Spring not create instances of this one
    @PreAuthorize("hasRole('ROLE_ADMIN')") // all methods will inherit this behavior
    interface BaseRepository<T, ID extends Serializable> extends Repository<T, ID> {}
    

    and then have all of your Repository's extend BaseRepository.

    Question 2: Security

    I'm going to be a little more general on this one.

    In order to correctly regulate access to entities within your application and define what-can-see-what, you should always separate your project into different layers.

    A good starting point would be:

    • layer-web (or presentation-layer): access to layer-business, no access to the db-layer. Can see DTO models but not DB models
    • layer-business (or business-layer): access to the db-layer but no access to the DAO
    • layer-db (or data-layer): convert DTO -> DB model. Persist objects and provide query results

    In your case, I believe that the right thing to do, would be therefore to check the role in the layer-business, before the request even reaches the Repository class.

    @Service
    public interface BookService {
    
        @PreAuthorize("hasRole('ROLE_ADMIN')")
        ActionResult saveToDatabase(final BookDTO book);
    }
    

    or, as seen before

    @Service
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public interface BookService {
    
        ActionResult saveToDatabase(final BookDTO book);
    }
    

    Also, ensuring a user can modify only its own objects can be done in many ways.

    Spring provides all necessary resources to do that, as this answer points out.

    Or, if you are familiar with AOP you can implement your own logic.

    E.g (dummyCode):

    @Service
    public interface BookService {
        // custom annotation here
        @RequireUserOwnership(allowAdmin = false)
        ActionResult saveToDatabase(final BookDTO book);
    }
    

    And the check:

    public class EnsureUserOwnershipInterceptor implements MethodInterceptor {
    
        @Autowired
        private AuthenticationService authenticationService;
    
        @Override
        public Object invoke(Invocation invocation) throws Throwable {
            // 1. get the BookDTO argument from the invocation
            // 2. get the current user from the auth service
            // 3. ensure the owner ID and the current user ID match
            // ...
        }
    }
    

    Useful resources about AOP can be found here and here.


    Question 3: DTO's and DB models

    Should I DTOs at all?

    Yes, yes you should. Even if your projects has only a few models and your are just programming for fun (deploying only on localhost, learning, ...).

    The sooner you get into the habit of separating your models, the better it is.

    Also, conceptually, one is an object coming from an unknown source, the other represents a table in your database.

    How this relates to JpaRepositories? How to use DTOs with Spring auto serverd rest repos?

    Now that's the point! You can't put DTO's into @Repositorys. You are forced to convert one to another. At the same point you are also forced to verify that the conversion is valid.

    You are basically ensuring that DTOs (dirty data) will not touch the database in any way, and you are placing a wall made of logical constraints between the database and the rest of the application.

    Also I am aware of Spring integrating well with model-conversion frameworks.


    So, what are the advantages of a multi-layer / modular web-application?

    • Applications can grow very quickly. Especially when you have many developers working on it. Some developers tend to look for the quickest solution and implement dirty tricks or change access modifiers to finish the job asap. You should force people to gain access to certain resources only through some explicitly defined channels. The more rules you set from the beginning, the longer the correct programming pattern will be followed. I have seen banking application become a complete mess after less then a year. When a hotfix was required, changing some code would create two-three other bugs.

    • You may reach a point where the application is consuming too many OS resources. If you, let's say, have a module module-batch containing background-jobs for your application, it will be way easier to extract it and implement it into another application. If your module contains logic that queries the database, access any type of data, provides API for the front-end, ecc... you will be basically forced to export all your code into your new application. Refactoring will be a pain in the neck at that point.

    • Imagine you want to hire some database experts to analyze the queries your application does. With a well-defined and separated logic you can give them access only to the necessary modules instead of the whole application. The same applies to front-end freelancers ecc... I have lived this situation as well. The company wanted database experts to fix the queries done by the application but did not want them to have access to the whole code. At the end, they renounced to the database optimization because that would have exposed too much sensitive information externally.

    And what are the advantages of DTO / DB model separation?

    • DTO's will not touch the database. This gives you more security against attacks coming from the outside
    • You can decide what goes on the other side. Your DTO's do not need to implement all the fields as the db model. Actually you can even have a DAO map to many DTO's or the other way around. There is lots of information that shouldn't reach the front-end, and with the DTO's you can easily do that.
    • DTO are in general liter than @Entity models. Whereas entities are mapped (e.g @OneToMany) to other entities, DTO's may just contain the id field of the mapped objects.
    • You do not want to have database objects hanging around for too long; and neither being passed around by methods of your application. Many framework commit database transactions at the end of each method, which means any involuntary change done onto the database entity may be committed into the db.

    Personally, I believe that any respectful web-application should strongly separate layers, each with its responsibility and limited visibility to other layers.

    Differentiation between database models and data transfer objects is also a good pattern to follow.

    At the end this is only my opinion though; many argue that the DTO pattern is outdated and causes unnecessary code repetition any many argue that to much separation leans to difficulty in maintaining the code. So, you should always consult different sources and then apply what works best for you.

    Also interesting: