Getting Started with Spring Security: A Practical Guide for Spring Boot Developers
Learn how to integrate Spring Security into Spring Boot applications with practical Java examples covering authentication, authorization, Maven dependencies, BCrypt passwords, and API security.
Security is one of the first concerns developers encounter when building a production-ready web application. An application may have a well-designed REST API, a powerful database and a modern frontend, but without proper authentication and authorization, its resources can easily be exposed to unauthorized users.
For Java developers working with Spring, Spring Security
provides a comprehensive security framework for protecting web applications and
APIs.
In this practical guide, we will set up Spring Security in a
Spring Boot application, examine the dependencies involved, configure a simple
security policy, and look at some of the dependency-management issues
developers should understand before moving to more advanced authentication
mechanisms such as JWT, OAuth 2.0 and LDAP.
What is Spring Security?
Spring Security is the security framework used to protect
Spring-based applications.
It provides mechanisms for implementing common security
requirements such as:
- Authentication
- Authorization
- Password
management
- Role-based
access control
- Method-level
security
- Protection
against common web attacks
- OAuth
2.0
- OpenID
Connect
- LDAP
authentication
- Resource-server
security
- Session
management
One of its biggest advantages is that security can be
integrated into a Spring application without developers having to implement
authentication and authorization infrastructure from scratch.
For example, instead of writing custom code to determine
whether every request should be allowed or rejected, Spring Security can
intercept requests and apply a centrally defined security policy.
Creating a Spring Boot Project
The easiest way to start a Spring Security project is with Spring
Initializr.
Open:
start.spring.io
Create a Maven-based Spring Boot application and add the
following dependencies:
- Spring
Web
- Spring
Security
You can then generate and download the project.
A typical project might look like this:
The exact package structure is not important. What matters
is that the application contains the Spring Boot application class, a
controller and a security configuration.
Adding Spring Security to Maven
If you already have a Spring Boot application and want to
add Spring Security manually, add the following dependency to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Notice that there is no version specified.
This is intentional.
Spring Boot manages compatible dependency versions through
its dependency-management mechanism. As a result, developers normally do not
need to manually specify the Spring Security version when using the Spring Boot
starter.
Your dependencies might therefore look like this:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
This is generally the preferred approach for a Spring Boot
application.
What Happens When Spring Security Is Added?
This is one of the first things that surprises developers
who are new to Spring Security.
Suppose we create the following controller:
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public String
hello() {
return
"Hello from Code Digest";
}
}
Without Spring Security, a request to:
GET /api/hello
would normally return:
Hello from Code Digest
Now add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Start the application and visit the endpoint again.
You will find that the endpoint is no longer freely
accessible.
Spring Boot automatically applies a default security
configuration.
For a simple development application, Spring Boot can
generate a default user and password-based authentication mechanism. The
generated password is displayed in the application startup logs.
For example:
Using generated security password:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
This is useful for getting started, but it is not a
production authentication strategy.
Creating a Security Configuration
For a real application, developers normally define their own
security rules.
Create:
config/SecurityConfig.java
Then add:
package com.codedigest.securitydemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public
SecurityFilterChain securityFilterChain(
HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public").permitAll()
.anyRequest().authenticated()
);
return
http.build();
}
}
This small configuration already demonstrates one of the
most important concepts in Spring Security.
We are defining which requests are public and which
requests require authentication.
The rule:
.requestMatchers("/api/public").permitAll()
means that /api/public can be accessed without
authentication.
The rule:
.anyRequest().authenticated()
means every other request must be authenticated.
Creating Public and Protected Endpoints
Let's modify the controller:
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/public")
public String
publicEndpoint() {
return
"This endpoint is public";
}
@GetMapping("/private")
public String
privateEndpoint() {
return
"This endpoint requires authentication";
}
}
Now there are two endpoints:
GET /api/public
GET /api/private
The first one is public.
The second requires authentication.
This distinction is fundamental when building REST APIs.
For example, an e-commerce application might expose:
GET /api/products
publicly, while protecting:
GET /api/orders
POST /api/orders
DELETE /api/orders/{id}
behind authentication.
Understanding Authentication and Authorization
These two terms are often confused.
Authentication
Authentication answers:
Who are you?
For example, a user logs into an application using:
Email: user@example.com
Password: ********
The application verifies the credentials and establishes the
user's identity.
Authorization
Authorization answers:
What are you allowed to do?
Suppose a user successfully logs in.
That does not automatically mean the user can access
everything.
For example:
USER
├── View products
├── Create orders
└── View own orders
ADMIN
├── View products
├── Create orders
├── View all orders
└── Delete products
Authentication establishes the identity.
Authorization determines what that identity can access.
Spring Security supports both concepts.
Adding a Default User for Testing
For a simple demonstration, we can define an in-memory user.
Add the following to SecurityConfig:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User
.withUsername("developer")
.password("{noop}password")
.roles("USER")
.build();
return new
InMemoryUserDetailsManager(user);
}
The complete configuration becomes:
@Configuration
public class SecurityConfig {
@Bean
public
SecurityFilterChain securityFilterChain(
HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public").permitAll()
.anyRequest().authenticated()
)
.httpBasic();
return
http.build();
}
@Bean
public
UserDetailsService userDetailsService() {
UserDetails
user = User
.withUsername("developer")
.password("{noop}password")
.roles("USER")
.build();
return new
InMemoryUserDetailsManager(user);
}
}
The {noop} prefix tells Spring Security that the password is
stored without encoding.
This should only be used for demonstrations and testing.
Never store production passwords this way.
For a production application, use a password encoder such as
BCrypt.
Using BCrypt for Passwords
A better configuration is:
@Bean
public PasswordEncoder passwordEncoder() {
return new
BCryptPasswordEncoder();
}
Then create the user using the encoder:
@Bean
public UserDetailsService userDetailsService(
PasswordEncoder passwordEncoder) {
UserDetails user =
User
.withUsername("developer")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
This gives us:
@Bean
public PasswordEncoder passwordEncoder() {
return new
BCryptPasswordEncoder();
}
and:
@Bean
public UserDetailsService userDetailsService(
PasswordEncoder passwordEncoder) {
UserDetails user =
User
.withUsername("developer")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
return new
InMemoryUserDetailsManager(user);
}
The important lesson is that applications should never store
users' plaintext passwords.
Testing the Application
Start the Spring Boot application.
For example:
./mvnw spring-boot:run
Or, on Windows:
mvnw.cmd spring-boot:run
Now test the public endpoint:
curl http://localhost:8080/api/public
The response should be:
This endpoint is public
Now try:
curl http://localhost:8080/api/private
Because authentication is required, the request will not
succeed without credentials.
Provide the username and password:
curl -u developer:password \
http://localhost:8080/api/private
The response should be:
This endpoint requires authentication
We have therefore created a basic authenticated API without
implementing the authentication mechanism ourselves.
Understanding spring-boot-starter-security
One dependency:
<artifactId>spring-boot-starter-security</artifactId>
pulls in the Spring Security components needed by a typical
Spring Boot application.
This is an example of why Spring Boot starters are useful.
Instead of manually adding a collection of individual Spring
Security libraries, the starter provides a convenient dependency entry point.
The application can then build on top of Spring Boot's
dependency-management system.
What About Spring Security Versions?
Spring Security uses a:
MAJOR.MINOR.PATCH
versioning structure.
For example:
7.1.1
can be interpreted as:
7 = major
1 = minor
1 = patch
Major releases may introduce breaking changes.
Minor releases generally introduce enhancements while
maintaining compatibility expectations.
Patch releases are primarily intended for compatible fixes.
This distinction becomes important when upgrading an
existing application.
A major version upgrade should not be treated in exactly the
same way as installing a routine patch release.
Should You Manually Specify the Spring Security Version?
For most Spring Boot applications:
No.
If Spring Boot manages the dependency version, let Spring
Boot manage it.
For example:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
is preferable to manually attaching a Spring Security
version unless there is a specific reason to override the managed version.
Manually mixing versions can result in dependency conflicts.
For example, an application could accidentally combine:
Spring Boot version A
Spring Framework version B
Spring Security version C
when those versions were not intended to be used together.
That can produce errors that are difficult to diagnose
because the application may compile successfully before failing during startup
or runtime.
Overriding the Spring Security Version
There are situations where a developer may deliberately need
a different Spring Security version.
Spring Boot's dependency management can be overridden
through a property such as:
<properties>
<spring-security.version>7.1.1</spring-security.version>
</properties>
However, this should be done deliberately.
Before overriding a managed dependency, check compatibility
between:
- Spring
Boot
- Spring
Framework
- Spring
Security
- Other
Spring projects in the application
Dependency management is not simply about getting the newest
version of every library.
It is about getting a compatible set of libraries.
Additional Spring Security Modules
The core starter is not the only part of Spring Security.
Applications requiring additional functionality may need
additional modules.
For example, a project using OAuth 2.0 may require
OAuth-related dependencies.
Similarly, an application integrating LDAP authentication
may require LDAP support.
This follows a useful principle:
Add the Spring Security module that provides the feature
your application actually needs.
A simple username/password application does not necessarily
need every Spring Security module.
Using Spring Security Without Spring Boot
Spring Security can also be used without Spring Boot.
In that situation, developers have more responsibility for
dependency management.
Instead of relying on the Spring Boot dependency-management
system, Spring Security provides a Bill of Materials (BOM).
A Maven configuration can import the Spring Security BOM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>{spring-security-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Then individual Spring Security dependencies can be declared
without repeating their versions:
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
</dependencies>
The BOM helps keep the Spring Security modules on a
consistent version.
Maven Central and Spring Security
For normal General Availability releases, developers
typically do not need to add a special repository.
Maven Central is sufficient.
For example:
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
In a standard Maven setup, however, Maven Central is already
configured by default, so adding it manually is usually unnecessary.
What About SNAPSHOT Releases?
Developers experimenting with unreleased Spring versions may
encounter SNAPSHOT dependencies.
A SNAPSHOT repository can be configured when required:
<repository>
<id>spring-snapshot</id>
<name>Spring
Snapshot Repository</name>
<url>https://repo.spring.io/snapshot</url>
</repository>
Similarly, milestone or release-candidate builds can require
the Spring milestone repository.
These repositories are generally unnecessary for
applications using normal stable releases.
For production systems, stable General Availability releases
are normally the safer choice.
A Practical Spring Security Development Workflow
When starting a new Spring Boot application, a sensible
workflow is:
Step 1 — Create the project
Use Spring Initializr.
Step 2 — Add Spring Security
Select:
Spring Security
Step 3 — Create your controllers
Start with the application functionality before adding
complex security rules.
Step 4 — Define security boundaries
Identify:
Public endpoints
Authenticated endpoints
Admin endpoints
Step 5 — Configure SecurityFilterChain
For example:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http)
throws
Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
);
return
http.build();
}
This gives us three security levels:
/api/public/** →
anyone
/api/admin/** →
ADMIN role
everything else →
authenticated users
Step 6 — Choose the authentication mechanism
Depending on the application, this might be:
Session authentication
HTTP Basic
JWT
OAuth 2.0
OpenID Connect
LDAP
Step 7 — Test security independently
Do not test only the successful login path.
Also test:
- Unauthenticated
requests
- Invalid
credentials
- Expired
credentials
- Insufficient
roles
- Access
to protected resources
- Access
to administrative resources
Common Mistakes to Avoid
1. Hard-coding every dependency version
Avoid doing this unnecessarily:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>...</version>
</dependency>
Let Spring Boot manage starter versions.
2. Storing plaintext passwords
Do not use:
.password("{noop}password")
in production.
Use an appropriate password encoder.
3. Making every endpoint public
Avoid:
.anyRequest().permitAll()
unless the application genuinely requires all endpoints to
be public.
4. Mixing incompatible Spring versions
Do not randomly combine Spring Boot, Spring Framework and
Spring Security versions.
Understand the dependency-management model first.
5. Assuming authentication means authorization
A successfully authenticated user should not automatically
receive administrator privileges.
Define roles and authorities explicitly.
Conclusion
Adding Spring Security to a Spring Boot application can be
remarkably simple. In many cases, all that is required to get started is:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
The real work begins when defining how the application's
resources should be protected.
Developers should understand the difference between
authentication and authorization, identify public and protected resources,
configure a SecurityFilterChain, and select an authentication mechanism
appropriate for the application.
For Spring Boot projects, allowing Spring Boot to manage
Spring Security's dependency versions is generally the simplest approach. For
standalone Spring Security applications, the Spring Security BOM provides a way
to keep related modules aligned.
From this foundation, developers can move toward more
advanced security architectures involving BCrypt password storage, JWT, OAuth
2.0, OpenID Connect, LDAP and role-based access control.
The important lesson is that security should not be added as
an afterthought. It should be treated as part of the application's architecture
from the beginning.