Business Scenario
In this lab, we will enhance the Medicare backend by integrating Spring Security to protect REST APIs and authenticate users using email and password.
Pre-Lab Preparation
Phase 8 — Spring Security Fundamentals + Basic Authentication
Authentication vs Authorization
1
Module:
1) Mastering Springboot Security
Authentication
Authorization
Question: "Who are you?"
Question: "What can you do?
Purpose: Confirms identity.
Purpose: Grants or denies permissions.
Example:
Example:
Email: rahul@gmail.com
Password: Rahul@123Rahul is authenticated
Rahul Role = PATIENT
View Doctors?
Book Appointment
Create Prescription
Add Spring Security Dependency
2
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>What Happens Immediately?
GET /api/doctors
will no longer behave like before.
Postman ↓ Security Filters ↓ DispatcherServlet ↓ Controller
Add Password to User
3
@NotBlank(message = "Password is required")
@Size(min = 8,max = 100,message = "Password must be between 8 and 100 characters)
@Column(nullable = false)
private String password;Add Password to User
4
@NotBlank(message = "Password is required")
@Size(min = 8,max = 100,message = "Password must be between 8 and 100 characters)
@Column(nullable = false)
private String password;public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}Important Database Note
DROP DATABASE medicare_db;
CREATE DATABASE medicare_db;Note: We are dropping and recreating the development database because the User entity now includes a new password field required for Spring Security. This allows Hibernate to generate the updated table structure. This will permanently delete all existing data, so proceed only if the data is disposable.
Passwords Must NEVER Be Stored Directly
password = Rahul@123
inside the database.
email password -------------------------------- rahul@gmail.com Rahul@123
$2a$10$...
using: BCrypt
Create Security Package
5
com.medicare/ └── security
We Now Need User DTOs
6
dto/ ├── UserRequest.java └── UserResponse.java
Create UserRequest
7
package com.medicare.dto;
import com.medicare.entity.Role;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserRequest {
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Email is required")
@Email(message = "Please provide a valid email")
private String email;
@NotBlank(message = "Phone number is required")
private String phone;
@NotBlank(message = "Password is required")
@Size(
min = 8,
max = 100,
message = "Password must be between 8 and 100 characters"
)
private String password;
@NotNull(message = "Role is required")
private Role role;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
} @NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Email is required")
@Email(message = "Please provide a valid email")
private String email;
@NotBlank(message = "Phone number is required")
private String phone;
@NotBlank(message = "Password is required")
@Size(
min = 8,
max = 100,
message = "Password must be between 8 and 100 characters"
)
private String password;
@NotNull(message = "Role is required")
private Role role;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
} public void setRole(Role role) {
this.role = role;
}
}Create UserResponse
8
package com.medicare.dto;
import com.medicare.entity.Role;
public class UserResponse {
private Long id;
private String name;
private String email;
private String phone;
private Role role;
public UserResponse(
Long id,
String name,
String email,
String phone,
Role role) { this.id = id;
this.name = name;
this.email = email;
this.phone = phone;
this.role = role;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public Role getRole() {
return role;
}
}DATABASE ENTITY User ├── name ├── email ├── phone ├── password └── role
API RESPONSE UserResponse ├── name ├── email ├── phone └── role
Update UserRepository
9
Optional<User> findByEmailIgnoreCase(
String email
);Create PasswordEncoder
10
config/ ├── SecurityConfig.java
package com.medicare.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}Update UserService
11
Receive plain password ↓ BCrypt encode ↓ Store encoded password
package com.medicare.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.medicare.dto.UserRequest;
import com.medicare.dto.UserResponse;
import com.medicare.entity.User;
import com.medicare.exception.UserNotFoundException;
import com.medicare.repository.UserRepository;@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(
UserRepository userRepository,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public UserResponse createUser(
UserRequest request) {
if (userRepository.existsByEmail(
request.getEmail())) {
throw new IllegalArgumentException(
"Email is already registered"
);
}
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone()); /*
* Never store a plain-text password.
* BCrypt converts it into a secure hash.
*/
user.setPassword(
passwordEncoder.encode(
request.getPassword()
)
);
user.setRole(request.getRole());
User savedUser = userRepository.save(user);
return mapToUserResponse(savedUser);
}
public List<UserResponse> getAllUsers() {
List<User> users =
userRepository.findAll();
List<UserResponse> responses =
new ArrayList<>();
for (User user : users) {
responses.add(mapToUserResponse(user));
} return responses;
}
public UserResponse getUserById(Long id) {
User user = userRepository
.findById(id)
.orElseThrow(() ->
new UserNotFoundException(
"User not found with id: " + id
)
);
return mapToUserResponse(user);
}
private UserResponse mapToUserResponse(
User user) {
return new UserResponse(
user.getId(),
user.getName(),
user.getEmail(),
user.getPhone(),
user.getRole()
);
}
}Update UserController
12
@PostMapping
public ResponseEntity<UserResponse>
createUser( @Valid @RequestBody UserRequest request) {
UserResponse response = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}Create CustomUserDetailsService
13
package com.medicare.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.medicare.entity.User;
import com.medicare.repository.UserRepository;
@Service
public class CustomUserDetailsService
implements UserDetailsService {security/ └── CustomUserDetailsService.java
private final UserRepository userRepository;
public CustomUserDetailsService( UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(
String username)
throws UsernameNotFoundException {
User user = userRepository
.findByEmailIgnoreCase(username)
.orElseThrow(() ->
new UsernameNotFoundException(
"User not found with email: "
+ username
)
);
return org.springframework.security
.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
}
}Why Do We Have Two User Classes?
We have: com.medicare.entity.User Our database entity. And Spring provides -org.springframework.security.core.userdetails.User Spring Security representation.
What Does .roles() Do?
Create DaoAuthenticationProvider
14
@Bean
public AuthenticationProvider authenticationProvider(
CustomUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider(userDetailsService);
provider.setPasswordEncoder( passwordEncoder);
return provider;
}import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import com.medicare.security.CustomUserDetailsService;@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.
AuthenticationConfiguration;HTTP Request ↓ Spring Security ↓ AuthenticationManager ↓ ProviderManager
↓ DaoAuthenticationProvider ↓ CustomUserDetailsService ↓ UserRepository ↓ MySQL ↓ PasswordEncoder ↓ Authentication Result
Configure SecurityFilterChain
15
package com.medicare.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.
AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.medicare.security.CustomUserDetailsService;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider(
CustomUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider(
userDetailsService
);
provider.setPasswordEncoder(
passwordEncoder
);
return provider;
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration)
throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
AuthenticationProvider authenticationProvider)
throws Exception {
http
/*
* For this REST/Postman learning phase,
* requests are authenticated with HTTP Basic.
*/
.csrf(csrf -> csrf.disable())
.authenticationProvider(authenticationProvider)
.authorizeHttpRequests(auth ->
auth
// Health check is public
.requestMatchers(
"/api/health"
)
.permitAll()
/*
* Temporarily public so we can
* create test users.
*
* Phase 9 will move registration
* into /api/auth/register.
*/
.requestMatchers(HttpMethod.POST,"/api/users")
.permitAll()
// Everything else requires login
.anyRequest()
.authenticated()
)
.httpBasic(
Customizer.withDefaults()
);
return http.build();
}
}Important Note About CSRF
Create Our First Secured User
16
POST http://localhost:8080/api/users
{
"name": "Rahul Shah",
"email": "rahul@example.com",
"phone": "9876543210",
"password": "Rahul@123","role": "PATIENT" }
{
"id": 1,
"name": "Rahul Shah",
"email": "rahul@example.com",
"phone": "9876543210",
"role": "PATIENT"
}Check MySQL
17
SELECT id, name, email, password, role FROM users;id: 1 name: Rahul Shah email: rahul@example.com password: $2a$10$................... role: PATIENT
Try Protected API Without Authentication
18
GET http://localhost:8080/api/doctors
Configure Basic Auth in Postman
19
Authorization ↓ Type ↓ Basic Auth
Username: rahul@example.com Password: Rahul@123
Use Wrong Password
20
Username: rahul@example.com Password: WrongPassword
GET /api/doctors
Create Doctor User
21
POST /api/users
{
"name": "Dr. Ananya Sharma",
"email": "ananya@example.com",
"phone": "9876543211",
"password": "Doctor@123",
"role": "DOCTOR"
}Rahul : PATIENT Dr. Ananya : DOCTOR
SecurityContextHolder ↓ SecurityContext ↓ Authentication ├── principal ├── credentials └── authorities
SecurityContextHolder
Create /api/users/me
22
@GetMapping("/me")
public ResponseEntity<Map<String, Object>> getCurrentUser() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
Map<String, Object> response = new LinkedHashMap<>();
response.put("username",authentication.getName());
response.put("authorities",authentication.getAuthorities());
return ResponseEntity.ok(response);
}import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;Test Current User
23
rahul@example.com Rahul@123
GET http://localhost:8080/api/users/me
{
"username": "rahul@example.com",
"authorities": [
{
"authority": "ROLE_PATIENT"
}
]
}Authentication ├── Principal │ ↓ │ rahul@example.com ├── Credentials │ ↓ │ password-related authentication data └── Authorities ↓ ROLE_PATIENT
Understand Authentication
24
The Authentication object represents authenticated-user information and includes the principal, credentials, and granted authorities.
We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You have successfully integrated Spring Security into the Medicare backend and implemented Basic Authentication using BCrypt password encryption.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) Mastering Springboot Security