Content ITV
This is Itvedant Content department
Business Scenario
In this lab, we will enhance the Medicare backend by implementing Role-Based Access Control (RBAC) and ownership security using Spring Security and JWT authentication.
Pre-Lab Preparation
Module:
1) Mastering Springboot Security
WHO is making the request?
WHAT is that user allowed to do?
WHAT is that user allowed to do?
Our Final Role Rules
PATIENT DOCTOR
PATIENT can :
Login
View/search doctors
Book an appointment
View own appointments
Create DoctorProfile
Modify DoctorProfile
View another patient's appointments
View doctor's private schedule
PATIENT cannot :
View own appointment details
Cancel own appointment
View own profileComplete an appointment Create prescriptions later
DOCTOR can :
Login
Search/view doctors
Manage own DoctorProfile
View own assigned appointments
View appointment assigned
Mark own appointment COMPLETED
Create prescriptions laterBook appointment as PATIENT
View another doctor's schedule
Modify another doctor's profile
View unrelated patient appointmentsDOCTOR cannot :
First Important Security Fix — Appointment Responses
dto/ └─ AppointmentResponse.java
Create AppointmentResponse
1
package com.medicare.dto;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.medicare.entity.AppointmentStatus;
import com.medicare.entity.PaymentStatus;
import com.medicare.entity.TimeSlot;
public class AppointmentResponse {
private Long id;
private Long patientId;
private String patientName;
private Long doctorId;
private String doctorName;
private String specialization;
private LocalDate appointmentDate;
private TimeSlot timeSlot;
private BigDecimal consultationFee;
private AppointmentStatus appointmentStatus; private PaymentStatus paymentStatus;
private LocalDateTime createdAt;
public AppointmentResponse(
Long id,
Long patientId,
String patientName,
Long doctorId,
String doctorName,
String specialization,
LocalDate appointmentDate,
TimeSlot timeSlot,
BigDecimal consultationFee,
AppointmentStatus appointmentStatus,
PaymentStatus paymentStatus,
LocalDateTime createdAt) {
this.id = id;
this.patientId = patientId;
this.patientName = patientName;
this.doctorId = doctorId;
this.doctorName = doctorName;
this.specialization = specialization;
this.appointmentDate = appointmentDate;
this.timeSlot = timeSlot;
this.consultationFee = consultationFee;
this.appointmentStatus = appointmentStatus;
this.paymentStatus = paymentStatus;
this.createdAt = createdAt; }
public Long getId() {
return id;
}
public Long getPatientId() {
return patientId;
}
public String getPatientName() {
return patientName;
}
public Long getDoctorId() {
return doctorId;
}
public String getDoctorName() {
return doctorName;
}
public String getSpecialization() {
return specialization;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
} public TimeSlot getTimeSlot() {
return timeSlot;
}
public BigDecimal getConsultationFee() {
return consultationFee;
}
public AppointmentStatus getAppointmentStatus() {
return appointmentStatus;
}
public PaymentStatus getPaymentStatus() {
return paymentStatus;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
}Fix AppointmentRequest
2
{
"patientId": 5
}package com.medicare.dto;
import java.time.LocalDate;
import com.medicare.entity.TimeSlot;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotNull;
public class AppointmentRequest { @NotNull(message = "Doctor id is required")
private Long doctorId;
@NotNull(message = "Appointment date is required")
@FutureOrPresent(message = "Appointment date cannot be in the past")
private LocalDate appointmentDate;
@NotNull(message = "Time slot is required")
private TimeSlot timeSlot;
public Long getDoctorId() {
return doctorId;
}
public void setDoctorId(Long doctorId) {
this.doctorId = doctorId;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(
LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public TimeSlot getTimeSlot() {
return timeSlot;
} public void setTimeSlot(
TimeSlot timeSlot) {
this.timeSlot = timeSlot;
}
}How Do We Know the Patient Now ?
JWT ↓ email ↓ SecurityContextHolder ↓ Authentication
Enable Method Security
3
import org.springframework.security.config.annotation.method.configuration.
EnableMethodSecurity;Now we can use: @PreAuthorize(...)
@Configuration
@EnableMethodSecurity
public class SecurityConfig {Understanding hasRole()
PATIENT ↓ ROLE_PATIENT
DOCTOR ↓ ROLE_DOCTOR
Update DoctorProfileRepository
4
Optional<DoctorProfile> findByUserId( Long userId );boolean existsByUserId(Long userId);
Optional<DoctorProfile> findByUserId(
Long userId
);import java.util.Optional;Secure Doctor Profile Ownership
5
JWT ↓ Current Doctor User ↓ Their DoctorProfile
package com.medicare.dto;
import java.math.BigDecimal;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
public class DoctorProfileRequest {
@NotBlank(message = "Specialization is required")
@Size(max = 100)
private String specialization;
@NotNull(message = "Experience is required")
@Min(0)
@Max(60)
private Integer experienceYears;
@NotNull(message = "Consultation fee is required")
@Positive
private BigDecimal consultationFee;
@Size(max = 500)
private String photoUrl;
public String getSpecialization() {
return specialization;
}
public void setSpecialization(
String specialization) {
this.specialization = specialization;
} public Integer getExperienceYears() {
return experienceYears;
}
public void setExperienceYears(
Integer experienceYears) {
this.experienceYears = experienceYears;
}
public BigDecimal getConsultationFee() {
return consultationFee;
}
public void setConsultationFee(
BigDecimal consultationFee) {
this.consultationFee = consultationFee;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(
String photoUrl) {
this.photoUrl = photoUrl;
}
}Doctor Profile Endpoints Change
6
PUT /api/doctors/{doctorId}
DELETE /api/doctors/{doctorId}for profile management.
POST /api/doctors/profile GET /api/doctors/profile/me PUT /api/doctors/profile/me DELETE /api/doctors/profile/me
Update DoctorService
7
@PreAuthorize("hasRole('DOCTOR')")
public DoctorResponse createMyProfile(DoctorProfileRequest request,String email) {
User user = findUserByEmail(email);
if (doctorRepository.existsByUserId(user.getId())) {
throw new IllegalArgumentException( "Doctor profile already exists");
}
DoctorProfile doctor = new DoctorProfile();
doctor.setUser(user);
doctor.setSpecialization(request.getSpecialization());
doctor.setExperienceYears(request.getExperienceYears());
doctor.setConsultationFee(request.getConsultationFee()); doctor.setPhotoUrl(request.getPhotoUrl());
return mapToDoctorResponse(doctorRepository.save(doctor));
}@PreAuthorize("hasRole('DOCTOR')")
public DoctorResponse getMyProfile(String email) {
User user = findUserByEmail(email);
DoctorProfile doctor = findDoctorByUserId(user.getId());
return mapToDoctorResponse(doctor);
}@PreAuthorize("hasRole('DOCTOR')")
public DoctorResponse updateMyProfile(String email,DoctorProfileRequest request) {
User user = findUserByEmail(email); DoctorProfile doctor = findDoctorByUserId(user.getId());
doctor.setSpecialization(request.getSpecialization());
doctor.setExperienceYears(request.getExperienceYears());
doctor.setConsultationFee(request.getConsultationFee());
doctor.setPhotoUrl(request.getPhotoUrl());
return mapToDoctorResponse(doctorRepository.save(doctor));
}@PreAuthorize("hasRole('DOCTOR')")
public void deleteMyProfile(String email) {
User user = findUserByEmail(email);
DoctorProfile doctor = findDoctorByUserId(user.getId());
doctorRepository.delete(doctor);
}Add Helper Methods
8
private User findUserByEmail(String email) {
return userRepository.findByEmailIgnoreCase(email).orElseThrow(() ->
new UserNotFoundException("User not found")
);
}private DoctorProfile findDoctorByUserId(Long userId) {
return doctorRepository.findByUserId(userId).orElseThrow(() ->
new DoctorNotFoundException("Doctor profile not found")
);
}Before proceeding to the next steps, we need to clean up the existing DoctorService code by removing outdated methods
Update DoctorController
9
@PostMapping("/profile")
public ResponseEntity<DoctorResponse>createMyProfile(
@Valid @RequestBodyDoctorProfileRequest request, Authentication authentication) {
DoctorResponse response = doctorService.createMyProfile(request,
authentication.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}@GetMapping("/profile/me")
public ResponseEntity<DoctorResponse>getMyProfile(Authentication authentication) {
return ResponseEntity.ok(doctorService.getMyProfile(authentication.getName())
);
}@PutMapping("/profile/me")
public ResponseEntity<DoctorResponse>updateMyProfile(
@Valid @RequestBody DoctorProfileRequest request, Authentication authentication) {
return ResponseEntity.ok(doctorService.updateMyProfile(
authentication.getName(),request)
);
}@DeleteMapping("/profile/me")
public ResponseEntity<Void> deleteMyProfile(Authentication authentication) {
doctorService.deleteMyProfile(authentication.getName());
return ResponseEntity.noContent().build();
}import org.springframework.security.core.Authentication;Fix Appointment Slot Query
10
existsByDoctorIdAndAppointmentDateAndTimeSlot(...)
boolean existsByDoctorIdAndAppointmentDateAndTimeSlotAndAppointmentStatusNot(
Long doctorId,
LocalDate appointmentDate,
TimeSlot timeSlot,
AppointmentStatus appointmentStatus
);Refactor AppointmentService
11
@PreAuthorize("hasRole('PATIENT')")
public AppointmentResponse createAppointment(AppointmentRequest request,
String email) {
User patient = findUserByEmail(email);
DoctorProfile doctor = doctorRepository.findById(request.getDoctorId())
.orElseThrow(() -> new DoctorNotFoundException(
"Doctor not found with id: "+ request.getDoctorId()
)
);
boolean booked = appointmentRepository
.existsByDoctorIdAndAppointmentDateAndTimeSlotAndAppointmentStatusNot(
doctor.getId(),request.getAppointmentDate(),request.getTimeSlot(),
AppointmentStatus.CANCELLED); if (booked) {
throw new AppointmentConflictException(
"Selected appointment slot is already booked");
}
Appointment appointment = new Appointment();
appointment.setPatient(patient);
appointment.setDoctor(doctor);
appointment.setAppointmentDate(request.getAppointmentDate());
appointment.setTimeSlot(request.getTimeSlot());
appointment.setConsultationFee(doctor.getConsultationFee());
appointment.setAppointmentStatus(AppointmentStatus.PENDING);
appointment.setPaymentStatus(PaymentStatus.UNPAID);
Appointment savedAppointment = appointmentRepository.save(appointment);
return mapToAppointmentResponse(savedAppointment);
}Patient — My Appointments
12
@PreAuthorize("hasRole('PATIENT')")
public List<AppointmentResponse>
getMyAppointments(String email) { User patient = findUserByEmail(email);
List<Appointment> appointments = appointmentRepository
.findByPatientId(patient.getId());
return mapAppointmentList(appointments);
}Doctor — My Schedule
13
@PreAuthorize("hasRole('DOCTOR')")
public List<AppointmentResponse>
getMyDoctorAppointments(
String email) {
User user = findUserByEmail(email);
DoctorProfile doctor = findDoctorByUserId(user.getId()); List<Appointment> appointments = appointmentRepository
.findByDoctorId(doctor.getId());
return mapAppointmentList(appointments);
}Secure Appointment by ID
14
@PreAuthorize("hasAnyRole('PATIENT', 'DOCTOR')")
public AppointmentResponse getAppointmentById( Long appointmentId,String email) {
Appointment appointment = findAppointmentById(appointmentId);
User user = findUserByEmail(email);
verifyAppointmentAccess(appointment,user);
return mapToAppointmentResponse(appointment);
}Ownership Check
15
private void verifyAppointmentAccess(Appointment appointment,User user) {
if (user.getRole() == Role.PATIENT) {
if (!appointment.getPatient().getId().equals(user.getId())) {
throw new AccessDeniedException( "You cannot access this appointment"); }
return;
}
if (user.getRole() == Role.DOCTOR) {
DoctorProfile doctor = findDoctorByUserId(user.getId());
if (!appointment.getDoctor().getId().equals(doctor.getId())) {
throw new AccessDeniedException("You cannot access this appointment");
}
}
}import org.springframework.security.access.AccessDeniedException;Patient Cancel Appointment
16
@PreAuthorize("hasRole('PATIENT')")
public AppointmentResponse cancelAppointment(Long appointmentId,String email) {
User patient = findUserByEmail(email);
Appointment appointment = findAppointmentById(appointmentId);
if (!appointment.getPatient().getId().equals(patient.getId())) {
throw new AccessDeniedException("You cannot cancel this appointment");
}
if (appointment.getAppointmentStatus()== AppointmentStatus.COMPLETED) {
throw new IllegalArgumentException(
"Completed appointment cannot be cancelled");
}
if (appointment.getAppointmentStatus() == AppointmentStatus.CANCELLED) {
throw new IllegalArgumentException("Appointment is already cancelled");
}
appointment.setAppointmentStatus(AppointmentStatus.CANCELLED);
return mapToAppointmentResponse(appointmentRepository.save(appointment));
}Doctor Complete Appointment
17
@PreAuthorize("hasRole('DOCTOR')")
public AppointmentResponse completeAppointment(Long appointmentId,String email) {
User user = findUserByEmail(email);
DoctorProfile doctor = findDoctorByUserId(user.getId());
Appointment appointment = findAppointmentById(appointmentId);
if (!appointment.getDoctor().getId().equals(doctor.getId())) {
throw new AccessDeniedException("You cannot complete this appointment");
}
if (appointment.getAppointmentStatus() == AppointmentStatus.CANCELLED) {
throw new IllegalArgumentException("Cancelled appointment cannot be completed");
}
appointment.setAppointmentStatus(AppointmentStatus.COMPLETED);
return mapToAppointmentResponse(appointmentRepository.save(appointment)
);
} return mapToAppointmentResponse(appointmentRepository.save(appointment)
);
}Appointment Mapping
18
private AppointmentResponse mapToAppointmentResponse(Appointment appointment) {
return new AppointmentResponse(appointment
.getId(),appointment.getPatient().getId(),
appointment.getPatient().getName(),
appointment.getDoctor().getId(),
appointment.getDoctor().getUser().getName(),
appointment.getDoctor().getSpecialization(),
appointment.getAppointmentDate(),
appointment.getTimeSlot(),
appointment.getConsultationFee(),
appointment.getAppointmentStatus(),
appointment.getPaymentStatus(), appointment.getCreatedAt()
);
}private List<AppointmentResponse>mapAppointmentList(
List<Appointment> appointments) {
List<AppointmentResponse> responses = new ArrayList<>();
for (Appointment appointment : appointments) {
responses.add(mapToAppointmentResponse(appointment));
}
return responses;
}Update AppointmentController
19
@PostMapping
public ResponseEntity<AppointmentResponse>
createAppointment(@Valid @RequestBody AppointmentRequest request,
Authentication authentication) {
AppointmentResponse response = appointmentService.createAppointment(
request,authentication.getName()
);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}@GetMapping("/my")
public ResponseEntity<List<AppointmentResponse>>
getMyAppointments(Authentication authentication) {
return ResponseEntity.ok(appointmentService.getMyAppointments( authentication.getName()
)
);
}@GetMapping("/doctor/me")
public ResponseEntity<List<AppointmentResponse>>
getMyDoctorAppointments(Authentication authentication) {
return ResponseEntity.ok(appointmentService.getMyDoctorAppointments(
authentication.getName()
)
);
}@GetMapping("/{id}")public ResponseEntity<AppointmentResponse>
getAppointmentById( @PathVariable Long id,Authentication authentication) {
return ResponseEntity.ok(appointmentService.getAppointmentById(
id,authentication.getName()
)
);
}@PatchMapping("/{id}/cancel")
public ResponseEntity<AppointmentResponse>
cancelAppointment(@PathVariable Long id, Authentication authentication) {
return ResponseEntity.ok(appointmentService.cancelAppointment(
id,authentication.getName()
)
);
}@PatchMapping("/{id}/complete")
public ResponseEntity<AppointmentResponse>
completeAppointment(@PathVariable Long id,Authentication authentication) {
return ResponseEntity.ok(appointmentService.completeAppointment(
id,authentication.getName()
)
);
}Remove Insecure Appointment Endpoints
20
GET /api/appointments GET /api/appointments/patient/{patientId} GET /api/appointments/doctor/{doctorId}
PATIENT ↓ /appointments/my
DOCTOR ↓ /appointments/doctor/me
Remove Insecure Appointment Endpoints
21
public UserResponse getCurrentUser(String email) {
User user = userRepository.findByEmailIgnoreCase(email).orElseThrow(() ->
new UserNotFoundException("User not found")
);
return mapToUserResponse(user);
}@GetMapping("/me")
public ResponseEntity<UserResponse>getCurrentUser(Authentication authentication) { return ResponseEntity.ok(userService.getCurrentUser(authentication.getName())
);
}{
"id": 1,
"name": "Rahul Shah",
"email": "rahul@example.com",
"phone": "9876543210",
"role": "PATIENT"
}GET /api/users
GET /api/users/{id}Configure Endpoint-Level RBAC
22
from the controller for now.
.authorizeHttpRequests(auth -> auth
// Public APIs
.requestMatchers("/api/health","/api/auth/register","/api/auth/login")
.permitAll()
// Current user's profile
.requestMatchers("/api/users/me").authenticated()
// Doctor can manage only own profile .requestMatchers("/api/doctors/profile","/api/doctors/profile/**")
.hasRole("DOCTOR")
// Doctor search/listing
.requestMatchers(HttpMethod.GET,"/api/doctors/**")
.hasAnyRole("PATIENT", "DOCTOR")
// Patient books appointments
.requestMatchers(HttpMethod.POST,"/api/appointments")
.hasRole("PATIENT")
// Patient appointment history
.requestMatchers("/api/appointments/my").hasRole("PATIENT")
// Doctor schedule
.requestMatchers("/api/appointments/doctor/**").hasRole("DOCTOR")
// Patient cancellation
.requestMatchers(HttpMethod.PATCH,"/api/appointments/*/cancel")
.hasRole("PATIENT")
// Doctor completion
.requestMatchers(HttpMethod.PATCH,"/api/appointments/*/complete")
.hasRole("DOCTOR")
// Appointment details
.requestMatchers(HttpMethod.GET,"/api/appointments/*")
.hasAnyRole("PATIENT", "DOCTOR")
/* * Safer default: Anything we forgot to configure is denied.
*/
.anyRequest()
.denyAll()
)
Why Both SecurityConfig AND @PreAuthorize?
Yes — intentionally for sensitive business actions.
HTTP endpoint:
POST /appointments
↓
SecurityConfig:
PATIENT required
↓
Controller
↓
Service:
@PreAuthorize("hasRole('PATIENT')")Proper 401 and 403 JSON Responses
23
security/ └── CustomAuthenticationEntryPoint.java
package com.medicare.security;
import java.io.IOException;
import java.time.LocalDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.medicare.response.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tools.jackson.databind.json.JsonMapper;
@Component
public class CustomAuthenticationEntryPoint
implements AuthenticationEntryPoint {
private final JsonMapper jsonMapper;
public CustomAuthenticationEntryPoint(
JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException {
ErrorResponse errorResponse =
new ErrorResponse(
LocalDateTime.now(),
HttpStatus.UNAUTHORIZED.value(),
"Authentication is required",
request.getRequestURI()
);
response.setStatus(
HttpStatus.UNAUTHORIZED.value()
);
response.setContentType(
MediaType.APPLICATION_JSON_VALUE
);
response.getWriter().write(
jsonMapper.writeValueAsString(
errorResponse
)
);
}
}import java.io.IOException;
import java.time.LocalDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.medicare.response.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tools.jackson.databind.json.JsonMapper;
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final JsonMapper jsonMapper;
public CustomAuthenticationEntryPoint(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
@Override
public void commence(HttpServletRequest request,HttpServletResponse response,
AuthenticationException exception) throws IOException {
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.UNAUTHORIZED.value(),
"Authentication is required",
request.getRequestURI()
); response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(jsonMapper.writeValueAsString(errorResponse));
}
}Create CustomAccessDeniedHandler
24
security/ └── CustomAccessDeniedHandler.java
package com.medicare.security;
import java.io.IOException;
import java.time.LocalDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import com.medicare.response.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tools.jackson.databind.json.JsonMapper;
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
private final JsonMapper jsonMapper;
public CustomAccessDeniedHandler(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
@Override
public void handle(
HttpServletRequest request,HttpServletResponse response,
AccessDeniedException exception) throws IOException {
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(), HttpStatus.FORBIDDEN.value(),
"You do not have permission to access this resource",
request.getRequestURI()
);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(jsonMapper.writeValueAsString(errorResponse)
);
}
}Create CustomAccessDeniedHandler
25
.exceptionHandling(exception ->
exception.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
)Not authenticated - 401 UNAUTHORIZED Authenticated but wrong role - 403 FORBIDDEN
Important Difference — Role vs Ownership
Is Rahul a PATIENT? --> YESDoes not mean: Rahul can view every patient appointmentWe still need: Does this appointment belong to Rahul?
Authentication ↓ Role ↓ Ownership
Testing — Patient Books Appointment
26
Login as Rahul: ROLE_PATIENT
POST /api/appointments Authorization: Bearer <patient-token>
{
"doctorId": 1,
"appointmentDate": "2026-09-25",
"timeSlot": "MORNING_10_AM"
}patientId from the frontend because the backend automatically identifies the logged-in patient, Rahul, using the JWT token.Doctor Tries to Book Appointment
27
Because: DOCTOR ≠ PATIENT
Patient — My Appointments
28
GET /api/appointments/my Authorization: Bearer <patient-token>
Doctor Tries /appointments/my
29
Doctor — Own Schedule
30
GET /api/appointments/doctor/me Authorization: Bearer <doctor-token>
Patient Tries Doctor Schedule
31
GET /api/appointments/doctor/me
Test Patient Ownership
32
Rahul owns Appointment 5 Priya owns Appointment 6
GET /api/appointments/5
GET /api/appointments/6
Test Doctor Ownership
33
Appointment 5 belongs to: Dr. Ananya
GET /api/appointments/5
GET /api/appointments/5
Patient Cancel Own Appointment
34
PATCH /api/appointments/5/cancel
Another Patient Tries to Cancel It
35
PATCH /api/appointments/5/complete
Doctor Completes Own Appointment
36
Patient Tries Complete
37
Doctor Profile Security
38
POST /api/doctors/profile
{
"specialization": "Cardiologist",
"experienceYears": 8,
"consultationFee": 1200,
"photoUrl": "..."
}Patient Tries Creating Doctor Profile
39
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 implemented Role-Based Access Control (RBAC) and ownership security in the Medicare backend using Spring Security and JWT authentication.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) Mastering Springboot Security
By Content ITV