@PostAuthorize and Filtering

@PostAuthorize: Checking the Result

Sometimes you only know if the user may see something after loading it. @PostAuthorize runs after the method and can read the result as returnObject.

@Service
public class OrderService {

    @PostAuthorize("hasRole('ADMIN') or returnObject.customerEmail == authentication.name")
    public OrderDto findById(Long orderId) {
        return orderRepository.findById(orderId)
                .map(OrderDto::from)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
}

Note: The method still runs, so never use @PostAuthorize on methods that change data (the change happens before the check). It is for reads only.

@PreFilter and @PostFilter

@PostFilter removes items from a returned collection; filterObject is each element. @PreFilter does the same with a collection argument.

@PostFilter("hasRole('ADMIN') or filterObject.customerEmail == authentication.name")
public List<OrderDto> recentOrders() {
    return orderRepository.findTop50ByOrderByCreatedAtDesc().stream().map(OrderDto::from).toList();
}

Tip: Filtering happens in memory after the query. For big tables, filter in the query instead (findByCustomerEmail(authentication.getName())). Use @PostFilter only for small lists.

Request and Response

Example

Asha opens her own order and then someone else's:

curl -i -u [email protected]:asha123 localhost:8080/api/orders/1001
curl -i -u [email protected]:asha123 localhost:8080/api/orders/1002

Example output

HTTP/1.1 200 OK
{"id":1001,"customerEmail":"[email protected]","status":"SHIPPED","total":2998.0}

HTTP/1.1 403 Forbidden