← Back to Questions
Spring Boot

How do you secure REST APIs in Spring Boot?

Learn How do you secure REST APIs in Spring Boot? with simple explanations, real-time examples, interview tips and practical use cases.

How Do You Secure REST APIs in Spring Boot?

Securing REST APIs in Spring Boot means protecting backend endpoints from unauthorized access, invalid users, data theft, token misuse, and common web security attacks.

REST APIs are commonly used by web applications, mobile apps, microservices, and third-party clients. Because APIs expose business data and operations, they must be secured properly using authentication, authorization, encryption, validation, and security best practices.


Why REST API Security is Important

REST APIs often handle sensitive information such as user profiles, payments, orders, transactions, admin actions, course purchases, and personal data.

Without proper security, attackers may:

  • Access private user data
  • Call admin APIs without permission
  • Modify or delete important records
  • Steal authentication tokens
  • Perform brute-force attacks
  • Exploit weak validation

Main Ways to Secure REST APIs in Spring Boot

  • Use Spring Security
  • Use JWT authentication for stateless APIs
  • Apply role-based authorization
  • Encrypt passwords using BCrypt
  • Use HTTPS in production
  • Configure CORS properly
  • Validate all request data
  • Handle exceptions securely
  • Protect sensitive endpoints
  • Use rate limiting for public APIs

1. Add Spring Security Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

After adding this dependency, Spring Boot enables security automatically. By default, all endpoints become protected.


2. Add JWT Dependency

JWT is commonly used to secure REST APIs because REST APIs are usually stateless.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

3. Use BCrypt for Password Encryption

Never store passwords in plain text. Always hash passwords before saving them in the database.

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

Example:

String encodedPassword = passwordEncoder.encode("admin123");

4. Configure SecurityFilterChain

Modern Spring Security uses SecurityFilterChain instead of the older WebSecurityConfigurerAdapter.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
            .csrf(csrf -> csrf.disable())
            .cors(Customizer.withDefaults())
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(
                jwtAuthenticationFilter,
                UsernamePasswordAuthenticationFilter.class
            );

        return http.build();
    }

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

5. Create JWT Utility Class

@Component
public class JwtService {

    private static final String SECRET_KEY =
            "my-very-secure-secret-key-my-very-secure-secret-key";

    public String generateToken(String username) {

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(
                        new Date(System.currentTimeMillis() + 1000 * 60 * 60)
                )
                .signWith(
                        Keys.hmacShaKeyFor(SECRET_KEY.getBytes()),
                        SignatureAlgorithm.HS256
                )
                .compact();
    }

    public String extractUsername(String token) {

        return Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean isTokenValid(String token, UserDetails userDetails) {

        String username = extractUsername(token);

        return username.equals(userDetails.getUsername())
                && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {

        Date expiration = Jwts.parserBuilder()
                .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();

        return expiration.before(new Date());
    }
}

6. Create JWT Authentication Filter

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(
            JwtService jwtService,
            UserDetailsService userDetailsService) {

        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }

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

        String authHeader = request.getHeader("Authorization");

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        String token = authHeader.substring(7);
        String username = jwtService.extractUsername(token);

        if (username != null
                && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails =
                    userDetailsService.loadUserByUsername(username);

            if (jwtService.isTokenValid(token, userDetails)) {

                UsernamePasswordAuthenticationToken authToken =
                        new UsernamePasswordAuthenticationToken(
                                userDetails,
                                null,
                                userDetails.getAuthorities()
                        );

                authToken.setDetails(
                        new WebAuthenticationDetailsSource()
                                .buildDetails(request)
                );

                SecurityContextHolder
                        .getContext()
                        .setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }
}

7. Create Login API

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtService jwtService;

    public AuthController(
            AuthenticationManager authenticationManager,
            JwtService jwtService) {

        this.authenticationManager = authenticationManager;
        this.jwtService = jwtService;
    }

    @PostMapping("/login")
    public Map<String, String> login(
            @RequestBody LoginRequest request) {

        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.getUsername(),
                        request.getPassword()
                )
        );

        String token = jwtService.generateToken(request.getUsername());

        return Map.of("token", token);
    }
}

8. Secure APIs Using Roles

@RestController
@RequestMapping("/api/admin")
public class AdminController {

    @GetMapping("/dashboard")
    public String dashboard() {
        return "Admin Dashboard";
    }
}

This endpoint is protected by:

.requestMatchers("/api/admin/**").hasRole("ADMIN")

9. Use Method-Level Security

@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{id}")
public String deleteUser(@PathVariable Long id) {
    return "User deleted";
}

Method-level security is useful when access control depends on business logic, permissions, or roles.


10. Configure CORS Securely

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {

        CorsConfiguration config = new CorsConfiguration();

        config.setAllowedOrigins(List.of(
                "https://www.dhanishempower.com"
        ));

        config.setAllowedMethods(List.of(
                "GET", "POST", "PUT", "DELETE", "OPTIONS"
        ));

        config.setAllowedHeaders(List.of(
                "Authorization", "Content-Type"
        ));

        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source =
                new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", config);

        return source;
    }
}

11. Validate Request Data

Always validate request payloads before processing.

public class RegisterRequest {

    @NotBlank(message = "Name is required")
    private String name;

    @Email(message = "Invalid email address")
    private String email;

    @Size(min = 8, message = "Password must contain at least 8 characters")
    private String password;
}
@PostMapping("/register")
public String register(@Valid @RequestBody RegisterRequest request) {
    return "User registered";
}

12. Handle Security Exceptions Properly

Do not expose stack traces or internal server details in API responses.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Map<String, Object> handleAccessDenied() {

        return Map.of(
                "status", 403,
                "message", "Access denied"
        );
    }
}

Important REST API Security Best Practices

  • Use HTTPS in production
  • Never store plain-text passwords
  • Use short-lived access tokens
  • Use refresh tokens securely
  • Do not store sensitive data inside JWT payload
  • Restrict admin APIs using roles
  • Allow only trusted CORS origins
  • Validate every request body and parameter
  • Log suspicious authentication failures
  • Use rate limiting for login and public APIs

JWT Request Flow

  1. User logs in using username and password
  2. Spring Security validates credentials
  3. Server generates JWT token
  4. Client sends token in Authorization header
  5. JWT filter validates token
  6. SecurityContext is updated
  7. Protected API is accessed

Authorization Header Example

Authorization: Bearer eyJhbGciOiJIUzI1Ni...

Common HTTP Status Codes for Secured APIs

Status Code Meaning
200 OK Request successful
201 CREATED Resource created successfully
400 BAD REQUEST Invalid request data
401 UNAUTHORIZED User is not authenticated
403 FORBIDDEN User does not have permission
500 INTERNAL SERVER ERROR Server-side error

Session-Based Security vs JWT Security

Feature Session-Based Security JWT Security
State Stateful Stateless
Storage Server stores session Client stores token
Best For Traditional web apps REST APIs and microservices
Scalability Moderate High

Common Mistakes While Securing REST APIs

  • Using plain-text passwords
  • Allowing all CORS origins in production
  • Keeping JWT tokens valid for too long
  • Storing secrets directly in source code
  • Not validating request payloads
  • Exposing stack traces in API responses
  • Not protecting admin endpoints

Common Interview Questions

How do you secure REST APIs in Spring Boot?

REST APIs can be secured using Spring Security, JWT authentication, role-based authorization, password encryption, HTTPS, CORS configuration, request validation, and secure exception handling.

Why is JWT used for REST APIs?

JWT is stateless, scalable, and works well with REST APIs, mobile apps, and microservices.

Why do we disable CSRF for JWT APIs?

JWT APIs usually use Authorization headers instead of browser sessions, so CSRF protection is commonly disabled for stateless APIs.

What is the difference between 401 and 403?

401 means the user is not authenticated. 403 means the user is authenticated but does not have permission to access the resource.

How do you protect admin APIs?

Admin APIs can be protected using role-based rules such as hasRole("ADMIN") or method-level annotations like @PreAuthorize("hasRole('ADMIN')").


Conclusion

Securing REST APIs in Spring Boot requires multiple layers of protection. Spring Security provides authentication, authorization, filters, password encoding, JWT support, CORS integration, and method-level security.

A production-ready REST API should use HTTPS, JWT tokens, BCrypt password hashing, role-based access control, request validation, secure exception handling, and strict CORS rules.

Understanding REST API security is essential for Spring Boot developers because secure APIs are mandatory in enterprise applications, microservices, banking systems, payment platforms, and modern backend systems.

Why this Spring Boot question is important?

This interview question helps candidates understand real-time backend development concepts, practical problem solving, coding fundamentals, system design basics and production-ready application behavior.

Practice this question carefully for Java backend roles, Spring Boot developer interviews, microservices interviews, company interviews and full-stack developer preparation.

About the Author

Naresh Kumar is a Senior Java Backend Engineer with experience building enterprise applications using Java, Spring Boot, Microservices, Docker, Kubernetes and Cloud technologies.