Getting Started with Spring Boot Security Antmatchers

Oct 15, 2021


As a backend developer, the API you build will typically be for consumption by other applications or services. The sensitivity of the data carried by an application varies from application to application.

Hence, it is ideal that only authenticated users have access to these data based on their authorization level when building a backend service.

Key takeaways

In this article:

  • We’ll have an overview of the need to secure your application.
  • We’ll dive further into the Springboot Antmatcher technique of securing an application.


  • Fundamental knowledge of the Springboot framework.
  • Fundamental understanding of Application Programming Interfaces (APIs). You can learn how to build a Springboot REST API here.
  • A suitable development environment such as IntelliJ.
  • Postman API testing tool or any suitable browser.

The need to secure your application

When building an application that is expected to transfer sensitive data, you want to ensure that this data does not get into the wrong hands.

An application that transfers customers’ financial details should be built to accommodate the most premium set of security protocols and measures. We should secure such a service enough to prevent data breaches, cyberattacks, data corruption and so on.

The integrity of the data generated by a service depends on the measures put in place to ensure that only authorized users can retrieve or manipulate that data.

Nevertheless, the kind of data generated and transferred determines how you should protect the service.


The antMatchers() is a Springboot HTTP method used to configure the URL paths from which the Springboot application security should permit requests based on the user’s roles. The antmatchers() method is an overloaded method that receives both the HTTP request methods and the specific URLs as its arguments.

Springboot uses the antmatchers() to protect URLs by binding patterns representing the application’s endpoints to specific users. Then it either permits or denies access to these URLs based on the roles or permissions of the users.

The following are some of the methods applied on antmatchers():

  • hasAnyRole(): This binds the URL to any user whose role is included in the configured roles created in the application. It receives a variable-length argument of roles.
  • hasRole(): This method receives a single role argument bound to the URL.
  • hasAuthority(): This method binds the URL to the granted authorities of the client. Any client who has been granted certain authorities is authorized to send a request to the URL.
  • hasAnyAuthority(): This binds the URL to any user whose granted authorities is included in the configured authorities/permissions created in the application. It receives a variable-length argument of granted authorities.
  • anonymous(): This binds the URL to an unauthenticated client.
  • authenticated(): This binds the URL to any authenticated client.

Let’s build an API to demonstrate

Let us proceed to explore the features of the antMatchers technique. We’ll build an API consisting of a Product model and its resources.

Project structure



The role of the client is either of the following:

  • Intern
  • Supervisor
  • Admin

The server grants the request from the client depending on whether or not the client is authorized to receive the resource from the URL.

Create an Enum class to declare the roles of the users.


public enum Roles {






Role Add a product View all products View product Update product Delete product
Intern Yes Yes
Supervisor Yes Yes Yes -`
Admin Yes Yes Yes Yes Yes

The dependencies required for this API are:

  • Spring web




  • Spring security




  • Guava maven







I’ll be using the PostgreSQL database management server to store the instances of the Product model. You can use any database of your choice.






Create a Product database in your Database management server.

database creation

Database configuration

Add the following code to your file to configure the database to your Springboot application.




spring.jpa.hibernate.ddl-auto=create-drop = org.hibernate.dialect.PostgreSQLDialect\_sql = true


Replace <username> and <password> with your database server’s actual username and password.

Creating the model



public class Product {

    // Autogenerate the primary key    

        name = "product\_sequence",

        sequenceName = "product\_sequence",

       allocationSize = 1



        strategy = GenerationType.SEQUENCE,

       generator = "product\_sequence"


    private Integer productId;

    private String name;

    private String description;

    private Double price;

    public Product(Integer productId, String name, String desc, Double price){

        this(name, desc, price);

        this.productId = productId;


   public Product(){


   public Product(String name, String desc, Double price) { = name;

        this.description = desc;

       this.price = price;


    public String getDescription() {

       return description;


    public void setDescription(String description) {

        this.description = description;


   public Number getPrice() {

        return price;


   public void setPrice(Double price) {

      this.price = price;


   public Integer getProductId() {

       return productId;


   public String getName() {

      return name;


   public void setName(String name) { = name;



The Product model in the code above has an autogenerated primary key – productId generated through the @SequenceGenerator and GeneratedValue annotations.

Repository interface

This is the portion of the application that interacts with the database. The interface extends JpaRepository interface, which is a Springboot built-in database interactive interface.


public interface ProductRepository extends JpaRepository<Product, Integer> {



This interface contains the declaration of the services that the API will provide. It is not always mandatory to declare this interface. A developer can skip the service interface and proceed with the implementation.

However, this approach is advisable because it improves the encapsulation of your application.


public interface ProductService {

    Product addProduct(Product product);

    List<Product> getAllProducts();

    Product getProduct(Integer productId);

    void deleteProduct(Integer productId);

    Product updateProduct(Integer productId, String productName, String productDescription, Double price);


Service implementation

This contains the business logic of the API. Here, we implement the already pre-defined methods from the ProductService interface.


public class ProductServiceImpl implements ProductService{


    private ProductRepository productRepository;


    public Product addProduct(Product product) {




    public List<Product> getAllProducts() {

        return productRepository.findAll();



   public Product getProduct(Integer productId) {

        return productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("Invalid product id"));



   public void deleteProduct(Integer productId) {

       Product product = getProduct(productId);


   /* To update the value a property:
        - validate that the new value is not null nor empty.
        - validate that the new value is not the same as the old value to be replaced.
        - If the values are the same, skip the operation.

    public Product updateProduct(Integer productId, String productName, String productDescription, Double price) {

        Product product = getProduct(productId);

        boolean emptyName = productName == null || productName.length() < 1;

       boolean emptyProductDesc = productDescription == null || productDescription.length() < 1;

        boolean validPrice = price != null && (price.compareTo((double) 0) > 0);

       if (!emptyName && !product.getName().equals(productName)) {



       if (!emptyProductDesc && !product.getDescription().equals(productDescription)) {


            return product;




All requests are received from the client and sent to the service for processing. 



public class ProductController {


    private ProductService productService;


    public Product addProduct(@RequestBody Product product){

        return productService.addProduct(product);


    // Get a product by its ID

   public Product getProduct(@PathVariable("productId") Integer productId){

        return productService.getProduct(productId);


    public List<Product> getAllProducts(){

        return productService.getAllProducts();


    public void deleteProduct(@PathVariable("productId") Integer productId){



    // The product ID is the only required argument. 
    @PutMapping(path = "/{productId}")
   public Product updateProduct(
        @PathVariable Integer productId,

        @RequestParam(required =false) String productName,

        @RequestParam(required =false) String productDesc,

       @RequestParam(required =false) Double price


        return productService.updateProduct(productId, productName, productDesc, price);



Security configurations

Finally, let’s create a class where we configure the security operations of the application.

A password encoder is required to encrypt the user’s password when provided for authentication. To achieve this, add the following code below either in your main application class or a separate class and annotate the class with the @Configuration annotation.

  // Create and configure an instance of a Password encoder to encrypt the users' passwords.

    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder(15);


The HTTP session is set as stateless; therefore, authentication is required for every request. This will ease up the demonstration of testing the endpoints on Postman.

If the client is not authorized to request the URL, the request is terminated with an error message, regardless of whether the client is authenticated.

Custom user details were created through the UserDetailsService() method, another overridden method of the WebSecurityConfigurerAdapter class.

The details are given as:

Username Role
timmy intern
john supervisor
sarah admin

The client’s login credentials are received through the Basic Authentication technique.

// The @EnableWebSecurity annotation maps this class as a program which configures the security of the application.

public class AppSecurityConfig extends WebSecurityConfigurerAdapter {


    private final PasswordEncoder passwordEncoder;

    public AppSecurityConfig(PasswordEncoder passwordEncoder) {

    this.passwordEncoder = passwordEncoder;



   protected void configure(HttpSecurity http) throws Exception {
    Stateless session enables authentication for every request. This would help ease the demonstration 
    of this tutorial on Postman.


           .antMatchers(HttpMethod.DELETE, "/api/v1/products/{productId}").hasRole( // Admin should be able to delete
           .antMatchers(HttpMethod.PUT, "/api/v1/products/{productId}").hasRole( // Admin should be able to update
            .antMatchers("/api/v1/products/add").hasAnyRole(, // Admin and Supervisor should be able to add product.
            .antMatchers("/api/v1/products").hasAnyRole(,, // All three users should be able to get all products.
            .antMatchers("/api/v1/products{productId}").hasAnyRole(,, // All three users should be able to get a product by id.


    // Setting up the details of the application users with their respective usernames, roles and passwords.
    protected UserDetailsService userDetailsService() {
    UserDetails timmy = User.builder()

    UserDetails john = User.builder()

    UserDetails sarah = User.builder()

    InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(timmy, john, sarah);
        return userDetailsManager;




Click on authorization and set the authorization type to Basic Auth.

forbidden add authorised add authorised get all authorised get all 2 forbidden delete authorised delete

As shown via Postman, we achieved authorizations based on the role of the client through the antmatchers() method. Unauthorized requests were forbidden, and authorized clients received a status code of 200.


In this article, we learned about the need of securing your application. We explored some of the methods of securing an application and proceeded to build an application that demonstrates how AntMatchers can be used to achieve a level of security on our applications.

Cyberattacks do not seem like they are going away for good anytime soon. As a backend developer, you are required to ensure your applications are not vulnerable to attacks.

The code for this project is available on my GitHub repo.

Happy coding.

Try Launching a Free Project in CloudFlow Today!


Introduction to Prisma with Docker

Introduction to Prisma with Docker

In this tutorial, we will learn more about the Prisma ecosystem. We will build a simple server that accesses Prisma to read its database schema and run it on a Docker container. At some point, you may need a database for your back-end application,...

read more