@PreFilter and @PostFilter in Spring Security
November 27, 2019
The @PreFilter
and @PostFilter
is a strong feature in Spring Security that filter collections or arrays on the basis of authorization. This is achieved using expression-based access control in Spring Security. The @PreFilter
filters the collections or arrays before executing method. The @PostFilter
filters the returned collection or arrays after executing the method. Spring security provides a built-in object named as filterObject
using which @PreFilter
and @PostFilter
performs the filtering task. The @PreFilter
and @PostFilter
can be used on service layer with @PreAuthorize
and @PostAuthorize
annotations.
Using @PreFilter and @PostFilter
Find the example.IBookService.java
package com.concretepage.service; import java.util.List; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import com.concretepage.bean.Book; public interface IBookService { @PreAuthorize ("hasRole('ROLE_READ')") @PostFilter ("filterObject.owner == authentication.name") public List<Book> getBooks(); @PreFilter("filterObject.owner == authentication.name") public void addBook(List<Book> books); }
filterObject
is built-in object on which filter operation is performed. In the above code, for the first method getBooks()
, we have used @PreAuthorize
and @PostFilter
annotations. Before executing method, user is authorized on the basis of role and then after executing, the returned object is filtered on the basis of owner. Second method addBook()
is only using @PreFilter
on the basis of owner. Find the implementation class of IBookService
file.
BookService.java
package com.concretepage.service; import java.util.ArrayList; import java.util.List; import com.concretepage.bean.Book; public class BookService implements IBookService { @Override public List<Book> getBooks() { Book b1 = new Book("A","rahim"); Book b2 = new Book("B","ram"); Book b3 = new Book("C","rahim"); Book b4 = new Book("D","ram"); List<Book> books = new ArrayList<Book>(); books.add(b1); books.add(b2); books.add(b3); books.add(b4); return books; } @Override public void addBook(List<Book> books) { System.out.println("Books Added."); for(Book b: books){ System.out.println(b.getName()+":"+b.getOwner()); } } }
Enable pre-post Annotations
Find the Spring Security XML configuration to enable@PreFilter
and @PostFilter
annotation.
<global-method-security pre-post-annotations="enabled"/>
@EnableGlobalMethodSecurity
as given below.
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true) public class SecurityConfig extends WebSecurityConfigurerAdapter { ------ }
Complete Example
We have configured two users ram and rahim. The ram has ROLE_READ and ROLE_WRITE whereas rahim has ROLE_READ.security-config.xml
<http auto-config="true" use-expressions="true"> <intercept-url pattern="/login" access="hasAnyRole('ROLE_READ','ROLE_WRITE')" /> <logout logout-success-url="/login" /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="ram" password="con1234" authorities="ROLE_READ,ROLE_WRITE" /> <user name="rahim" password="con1234" authorities="ROLE_READ" /> </user-service> </authentication-provider> </authentication-manager> <global-method-security pre-post-annotations="enabled"/> <beans:bean name="bookService" class="com.concretepage.service.BookService"/>
BookService
method, so that no access denied error is caught.
LoginController.java
package com.concretepage.security.controller; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.concretepage.bean.Book; import com.concretepage.service.IBookService; @Controller @RequestMapping("/login") public class LoginController { @Autowired public IBookService bookService; @RequestMapping(method = RequestMethod.GET) public String success(ModelMap map) { Book b1 = new Book("A","rahim"); Book b2 = new Book("B","ram"); Book b3 = new Book("C","rahim"); Book b4 = new Book("D","ram"); List<Book> books = new ArrayList<Book>(); books.add(b1); books.add(b2); books.add(b3); books.add(b4); if(hasRole("ROLE_WRITE")){ bookService.addBook(books); } if(hasRole("ROLE_READ")){ List<Book> bks = bookService.getBooks(); System.out.println("Books we got."); for(Book b: bks){ System.out.println(b.getName()+":"+b.getOwner()); } } map.addAttribute("msg", "Done Successfully"); return "success"; } private boolean hasRole(String role) { Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>)SecurityContextHolder.getContext(). getAuthentication().getAuthorities(); boolean hasRole = false; for (GrantedAuthority authority : authorities) { hasRole = authority.getAuthority().equals(role); if (hasRole) { break; } } return hasRole; } }
package com.concretepage.bean; public class Book { private String name; private String owner; public Book(String name,String owner){ this.name = name; this.owner = owner; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } }
Books Added. B:ram D:ram Books we got. B:ram D:ram
Books we got. A:rahim C:rahim