Secure Your RESTful API with Spring Boot 3.5, JWT, and MongoDB

by Didin J. on Jun 28, 2025 Secure Your RESTful API with Spring Boot 3.5, JWT, and MongoDB

Learn how to secure your REST API using Spring Boot 3.5.3, Spring Security 6, JWT authentication, and MongoDB for user storage in a modern Java stack.

Security is a critical part of any modern web application, especially when dealing with user authentication and sensitive data. With the latest updates in the Spring ecosystem—Spring Boot 3.5.3, Spring Security 6, and Java 17+—securing your backend API is easier and more robust than ever.

In this tutorial, you’ll learn how to build and secure a RESTful API using:

  • Spring Boot 3.5.3

  • Spring Security 6 with JWT-based stateless authentication

  • MongoDB as the user data store

  • BCrypt for password hashing

  • Role-based access control for protected endpoints

We’ll walk through creating a full authentication flow, from user registration to login, and protecting endpoints using JSON Web Tokens. This updated approach follows the latest best practices using SecurityFilterChain, removing the deprecated WebSecurityConfigurerAdapter.

By the end, you’ll have a solid foundation for building a secure, scalable Java backend with token-based authentication.


Generate a New Spring Boot Gradle Project

To create or generate a new Spring Boot Application or Project, simply go to Spring Initializer. Spring Initializr provides an extensible API to generate quickstart projects and to inspect the metadata used to generate projects, for instance, to list the available dependencies and versions. Fill in all required fields as below, then click on the Generate Project button.

Secure Your RESTful API with Spring Boot 3.5, JWT, and MongoDB - Spring Initializr

The project will automatically be downloaded as a Zip file. Next, extract the zipped project to your Java projects folder. In the project folder root, you will find the `build.gradle` file for registering dependencies, initially it looks like this.

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.3'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.djamware'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

Now, you can work with the source code of this Spring Boot Project using your own IDE or Text Editor. Next, we have to add the JWT library to the `build.gradle` as a dependency. Open and edit `build.gradle`, then add this line to dependencies after other implementations.

implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

Next, compile the Gradle Project by typing this command from the Terminal or CMD.

./gradlew compile

Or you can compile directly from STS by right-clicking the project name, then choose Gradle -> Refresh Gradle Project. Next, open and edit `src/main/resources/application.properties`, then add these lines.

spring.data.mongodb.database=springmongodb
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017


Create Product, User, and Role Java Model or Entity Classes

We will be creating all required models or entities for products, users, and roles. 

mkdir src/main/java/com/djamware/SecurityRest/models
touch src/main/java/com/djamware/SecurityRest/models/Product.java
touch src/main/java/com/djamware/SecurityRest/models/User.java
touch src/main/java/com/djamware/SecurityRest/models/Role.java

src/main/java/com/djamware/SecurityRest/models/Product.java

package com.djamware.SecurityRest.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String prodName;
    private String prodDesc;
    private Double prodPrice;
    private String prodImage;

    public Product() {
    }

    public Product(String id, String prodName, String prodDesc, Double prodPrice, String prodImage) {
        this.id = id;
        this.prodName = prodName;
        this.prodDesc = prodDesc;
        this.prodPrice = prodPrice;
        this.prodImage = prodImage;
    }

    // Getter and Setter
}

You can do it the same way as the above step for the User and Role class. Here's what the User class looks like.

package com.djamware.SecurityRest.models;

import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "users")
public class User {
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @DBRef
    private Set<Role> roles;

    // Getter and Setter
}

And the Role class will be like this.

package com.djamware.SecurityRest.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "roles")
public class Role {
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING)

    private String role;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}


Create Product, User, and Role Java Repository Interfaces

Next steps to create Product, User, and Role Repository Interfaces. 

mkdir src/main/java/com/djamware/SecurityRest/repositories
touch src/main/java/com/djamware/SecurityRest/repositories/ProductRepository.java
touch src/main/java/com/djamware/SecurityRest/repositories/UserRepository.java
touch src/main/java/com/djamware/SecurityRest/repositories/RoleRepository.java

Next, open and edit `src/main/java/com/djamware/SecurityRest/repositories/ProductRepository.java`, then make it like this.

package com.djamware.SecurityRest.repositories;

import org.springframework.data.repository.CrudRepository;

import com.djamware.SecurityRest.models.Product;

public interface ProductRepository extends CrudRepository<Product, String> {
    @Override
    void delete(Product deleted);
}

The same way can be applied to the User and Role repositories. So, the User Repository Interface will look like this.

package com.djamware.SecurityRest.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.djamware.SecurityRest.models.User;

public interface UserRepository extends MongoRepository<User, String> {
    User findByEmail(String email);
}

And the Role Repository Interface will look like this.

package com.djamware.SecurityRest.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.djamware.SecurityRest.models.Role;

public interface RoleRepository extends MongoRepository<Role, String> {
    Role findByRole(String role);
}


Create a Custom User Details Service

To implement authentication using existing User and Role from MongoDB, we have to create a custom user details service.

mkdir src/main/java/com/djamware/SecurityRest/services
touch src/main/java/com/djamware/SecurityRest/services/CustomUserDetailsService.java

Next, open and edit `src/main/java/com/djamware/SecurityRest/services/CustomUserDetailsService.java`, then give an annotation above the class name and implement the Spring Security User Details Service.

@Service
public class CustomUserDetailsService implements UserDetailsService {
}

Next, inject all required beans on the first line of the class bracket.

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Autowired
private PasswordEncoder bCryptPasswordEncoder;

Add a method to find a user by email field.

public User findUserByEmail(String email) {
    return userRepository.findByEmail(email);
}

Add a method to save a new user.

public void saveUser(User user) {
    user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
    user.setEnabled(true);
    Role userRole = roleRepository.findByRole("ADMIN");
    user.setRoles(new HashSet<>(Arrays.asList(userRole)));
    userRepository.save(user);
}

Override the Spring Security User Details to load the User by email.

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

    User user = userRepository.findByEmail(email);
    if(user != null) {
        List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
        return buildUserForAuthentication(user, authorities);
    } else {
        throw new UsernameNotFoundException("username not found");
    }
}

Add a method to get a set of Roles that are related to a user.

private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
    Set<GrantedAuthority> roles = new HashSet<>();
    userRoles.forEach((role) -> {
        roles.add(new SimpleGrantedAuthority(role.getRole()));
    });

    List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roles);
    return grantedAuthorities;
}

Add a method for authentication purposes.

private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
    return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}


Configure Spring Boot Security Rest

Now, the main purpose of this tutorial is to configure Spring Security Rest. First, we have to create a bean for JWT token generation and validation.

mkdir src/main/java/com/djamware/SecurityRest/configs
touch src/main/java/com/djamware/SecurityRest/configs/JwtTokenProvider.java
touch src/main/java/com/djamware/SecurityRest/configs/JwtTokenFilter.java
touch src/main/java/com/djamware/SecurityRest/configs/WebSecurityConfig.java

Next, open and edit JwtTokenProvider.java, then make it like this.

package com.djamware.SecurityRest.configs;

import java.security.Key;
import java.util.Date;
import java.util.Set;

import javax.crypto.SecretKey;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.djamware.SecurityRest.models.Role;
import com.djamware.SecurityRest.services.CustomUserDetailsService;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;

@Component
public class JwtTokenProvider {

    @Value("${security.jwt.token.secret-key:secret}")
    private String secret;

    @Value("${security.jwt.token.expire-length:3600000}") // 1 hour
    private long validityInMilliseconds;

    private Key secretKey;

    private final CustomUserDetailsService userDetailsService;

    public JwtTokenProvider(CustomUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @PostConstruct
    protected void init() {
        // Decode the secret key using Base64 and prepare the HMAC key
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        this.secretKey = Keys.hmacShaKeyFor(keyBytes);
    }

    public String createToken(String username, Set<Role> roles) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()
                .subject(username)
                .claim("roles", roles) // Use .claim() directly instead of Jwts.claims()
                .issuedAt(now)
                .expiration(expiry)
                .signWith(secretKey)
                .compact();
    }

    public Authentication getAuthentication(String token) {
        String username = getUsername(token);
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }

    public String getUsername(String token) {
        Claims claims = extractAllClaims(token);
        return claims.getSubject();
    }

    private Claims extractAllClaims(String token) {
        return Jwts
                .parser()
                .verifyWith(getSignInKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    private SecretKey getSignInKey() {
        byte[] keyBytes = secret.getBytes();
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String resolveToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        return (bearer != null && bearer.startsWith("Bearer ")) ? bearer.substring(7) : null;
    }

    public boolean validateToken(String token) {
        try {
            SecretKey key = getSignInKey();
            Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            throw new AuthenticationCredentialsNotFoundException("JWT was expired or incorrect");
        } catch (ExpiredJwtException e) {
            throw new AuthenticationCredentialsNotFoundException("Expired JWT token.");
        } catch (UnsupportedJwtException e) {
            throw new AuthenticationCredentialsNotFoundException("Unsupported JWT token.");
        } catch (IllegalArgumentException e) {
            throw new AuthenticationCredentialsNotFoundException("JWT token compact of handler are invalid.");
        }
    }
}

Next, update JwtTokenFilter.java:

package com.djamware.SecurityRest.configs;

import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class JwtTokenFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain)
            throws ServletException, IOException {

        String token = jwtTokenProvider.resolveToken(request);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

Finally, update WebSecurityConfig.java.

package com.djamware.SecurityRest.configs;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.djamware.SecurityRest.services.CustomUserDetailsService;

import jakarta.servlet.http.HttpServletResponse;

@Configuration
@EnableMethodSecurity
public class WebSecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;

    public WebSecurityConfig(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(jwtTokenProvider);

        http
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedEntryPoint()))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/login", "/api/auth/register").permitAll()
                        .requestMatchers("/api/products/**").hasAuthority("ADMIN")
                        .anyRequest().authenticated())
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationEntryPoint unauthorizedEntryPoint() {
        return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                "Unauthorized");
    }
}


Create Product and Authentication Spring MVC Controllers

Now it's time for the REST API Service endpoint. All REST API will be created from each controller. The product controller will handle the CRUD endpoint of the product, and the Authentication controller will handle the login and register endpoints.

mkdir src/main/java/com/djamware/SecurityRest/controllers
touch src/main/java/com/djamware/SecurityRest/controllers/ProductController.java
touch src/main/java/com/djamware/SecurityRest/controllers/AuthController.java
touch src/main/java/com/djamware/SecurityRest/controllers/AuthBody.java

Update ProductController.java.

package com.djamware.SecurityRest.controllers;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.djamware.SecurityRest.models.Product;
import com.djamware.SecurityRest.repositories.ProductRepository;

@RestController
public class ProductController {

    package com.djamware.SecurityRest.controllers;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.djamware.SecurityRest.models.Product;
import com.djamware.SecurityRest.repositories.ProductRepository;

@RestController
public class ProductController {
    @Autowired
    ProductRepository productRepository;

    @RequestMapping(method = RequestMethod.GET, value = "/api/products")
    public Iterable<Product> product() {
        return productRepository.findAll();
    }

    @RequestMapping(method = RequestMethod.POST, value = "/api/products")
    public String save(@RequestBody Product product) {
        productRepository.save(product);

        return product.getId();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/api/products/{id}")
    public Optional<Product> show(@PathVariable String id) {
        return productRepository.findById(id);
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/api/products/{id}")
    public Product update(@PathVariable String id, @RequestBody Product product) {
        Optional<Product> prod = productRepository.findById(id);
        if (product.getProdName() != null)
            prod.get().setProdName(product.getProdName());
        if (product.getProdDesc() != null)
            prod.get().setProdDesc(product.getProdDesc());
        if (product.getProdPrice() != null)
            prod.get().setProdPrice(product.getProdPrice());
        if (product.getProdImage() != null)
            prod.get().setProdImage(product.getProdImage());
        productRepository.save(prod.get());
        return prod.get();
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/api/products/{id}")
    public String delete(@PathVariable String id) {
        Optional<Product> product = productRepository.findById(id);
        productRepository.delete(product.get());

        return "product deleted";
    }
}
}

Update AuthBody.java.

package com.djamware.SecurityRest.controllers;

public class AuthBody {
    private String email;
    private String password;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Finally, update AuthController.java

package com.djamware.SecurityRest.controllers;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import static org.springframework.http.ResponseEntity.ok;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.djamware.SecurityRest.configs.JwtTokenProvider;
import com.djamware.SecurityRest.models.User;
import com.djamware.SecurityRest.repositories.UserRepository;
import com.djamware.SecurityRest.services.CustomUserDetailsService;

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    JwtTokenProvider jwtTokenProvider;

    @Autowired
    UserRepository users;

    @Autowired
    private CustomUserDetailsService userService;

    @SuppressWarnings("rawtypes")
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody AuthBody data) {
        try {
            String username = data.getEmail();
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
            String token = jwtTokenProvider.createToken(username, this.users.findByEmail(username).getRoles());
            Map<Object, Object> model = new HashMap<>();
            model.put("username", username);
            model.put("token", token);
            return ok(model);
        } catch (AuthenticationException e) {
            throw new BadCredentialsException("Invalid email/password supplied");
        }
    }

    @SuppressWarnings("rawtypes")
    @PostMapping("/register")
    public ResponseEntity register(@RequestBody User user) {
        User userExists = userService.findUserByEmail(user.getEmail());
        if (userExists != null) {
            throw new BadCredentialsException("User with username: " + user.getEmail() + " already exists");
        }
        userService.saveUser(user);
        Map<Object, Object> model = new HashMap<>();
        model.put("message", "User registered successfully");
        return ok(model);
    }
}


Run and Test Spring Boot Security Rest using Postman

Before running and testing the application, we have to populate the Role data first. Create src/main/java/com/djamware/SecurityRest/configs/DatabaseSeeder.java.

package com.djamware.SecurityRest.configs;

import java.util.Collections;

import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import com.djamware.SecurityRest.models.Role;
import com.djamware.SecurityRest.models.User;
import com.djamware.SecurityRest.repositories.RoleRepository;
import com.djamware.SecurityRest.repositories.UserRepository;

@Component
public class DatabaseSeeder implements CommandLineRunner {

    private final RoleRepository roleRepository;
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public DatabaseSeeder(RoleRepository roleRepository, UserRepository userRepository,
            PasswordEncoder passwordEncoder) {
        this.roleRepository = roleRepository;
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void run(String... args) {
        // Seed ADMIN role
        Role adminRole = roleRepository.findByRole("ADMIN");
        if (adminRole == null) {
            adminRole = new Role();
            adminRole.setRole("ADMIN");
            roleRepository.save(adminRole);
            System.out.println("Seeded ADMIN role.");
        }

        // Seed default admin user
        if (userRepository.findByEmail("[email protected]") == null) {
            User adminUser = new User();
            adminUser.setFullname("Admin");
            adminUser.setEmail("[email protected]");
            adminUser.setPassword(passwordEncoder.encode("admin123")); // Change in production!
            adminUser.setRoles(Collections.singleton(adminRole));
            userRepository.save(adminUser);
            System.out.println("Seeded admin user: [email protected] / admin123");
        }
    }
}

Next, make sure you have run the MongoDB server on your local machine, then run the Gradle application using this command.

./gradlew bootRun

Next, open the Postman application, then change the method to `GET` and address to `localhost:8080/api/products`, then click the Send button.

Secure Your RESTful API with Spring Boot 3.5, JWT, and MongoDB - Postman

You will see this response in the bottom panel of Postman.

{
    "timestamp": "2019-03-07T13:16:34.935+0000",
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/api/products"
}

Next, change the method to POST, then address to `localhost:8080/api/auth/register`, then fill the body with raw data as image below, then click the Send button.

Secure Your RESTful API with Spring Boot 3.5, JWT, and MongoDB - Postman with Token

You will get the response in the bottom panel of Postman.

{
    "message": "User registered successfully"
}

Next, change the address to `localhost:8080/api/auth/login` and change the body as below, then click the Send button.

{ "email":"[email protected]", "password": "q1w2we3r4" }

You will see this response in the bottom panel of Postman.

{
    "username": "[email protected]",
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJpbmZvQGRqYW13YXJlLmNvbSIsInJvbGVzIjpbeyJpZCI6IjVjODBjNjIzYjIwMTkxNGIyYTY5N2U4ZCIsInJvbGUiOiJBRE1JTiJ9XSwiaWF0IjoxNTUxOTY0OTc3LCJleHAiOjE1NTE5Njg1Nzd9.j5CHZ_LCmeQtdxQeH9eluxVXcOsHPWV1p8WnBn0CULo"
}

Copy the token, then back into the GET product. Add a header with the name `Authorization` and the value that is pasted from a token that is obtained by logging in with an additional `Bearer ` prefix (with a space) as below.

Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJpbmZvQGRqYW13YXJlLmNvbSIsInJvbGVzIjpbeyJpZCI6IjVjODBjNjIzYjIwMTkxNGIyYTY5N2U4ZCIsInJvbGUiOiJBRE1JTiJ9XSwiaWF0IjoxNTUxOTY0OTc3LCJleHAiOjE1NTE5Njg1Nzd9.j5CHZ_LCmeQtdxQeH9eluxVXcOsHPWV1p8WnBn0CULo

You should see this response after clicking the Send button.

[
    {
        "id": "5c80dc6cb20191520567b68a",
        "prodName": "Dummy Product 1",
        "prodDesc": "The Fresh Dummy Product in The world part 1",
        "prodPrice": 100,
        "prodImage": "https://dummyimage.com/600x400/000/fff"
    }
]

You can test the POST product with the token in headers using the same method.

Conclusion

With Spring Boot 3.5.3 and Spring Security 6, building a secure RESTful API is both modern and streamlined. By replacing traditional session-based security with JWT tokens, your application gains flexibility, scalability, and better support for stateless client-server communication—ideal for mobile apps, single-page applications, or microservices.

You’ve now built:

  • A secure backend with token-based authentication

  • A complete user registration and login flow

  • Protected REST endpoints using Spring Security’s latest configuration style

  • MongoDB integration to store user data

This is a solid base you can extend with role-based authorization, token refresh, and integration with frontends like Angular or React.

You can get the full source code from our GitHub.

That's just the basics. If you need more deep learning about Java and Spring Framework, you can take the following cheap course:

Thanks!