Skip to content

Session 8

REST API, Spring Security and Actuator

Updated View as Markdown

Actuator exposes health and metrics endpoints for operations. The session builds full CRUD REST APIs twice, once with annotations and once with XML configuration, then puts Spring Security’s default login in front of them.

Objectives

Do not copy. Read for understanding and the viva
  • Complete questions 33 to 36 of the manual: rest api, spring security and actuator
  • Prepare the deliverable before the lab and finish it during the session
  • Be ready to explain every step in the viva

Questions Covered

Do not copy. Read for understanding and the viva
QuestionRequirementStatus
Q33Add actuator dependency in Spring boot and Test Actuator (all available Endpoints)Complete
Q34Create REST API for CRUD operation using Spring Boot and Hibernate/JPA using annotationComplete
Q35Create a REST API for CRUD operation using Spring Boot and Hibernate/JPA using XML…Complete
Q36Add Spring Security dependency in the existing spring boot application created in…Complete

Preparation

Do not copy. Read for understanding and the viva
  • Actuator endpoints: /actuator/health, /actuator/info, /actuator/metrics; expose them with management.endpoints.web.exposure.include=* for the lab.
  • REST CRUD maps HTTP verbs: POST create, GET read, PUT update, DELETE delete. Test each with curl or Postman and keep the request and response in the record.
  • Adding spring-boot-starter-security alone protects every URL with a generated password printed in the console.

This session continues the admission-api project from Session 7. Listings marked “replaces” overwrite the Session 7 file of the same name. Nothing was executed here; every listing and every expected response was checked by reading against Spring Boot 3.3.

Question 33

Problem Statement

Write in lab record

Add actuator dependency in Spring boot and Test Actuator (all available Endpoints).

Solution

Write in lab record

Steps

  1. Confirm spring-boot-starter-actuator is in pom.xml (added in Question 29). If not, paste the block below inside <dependencies> and run Maven, Update Project.
  2. Replace application.properties with the listing: it keeps the Session 7 settings and adds the management.* and info.* keys.
  3. Restart. Open http://localhost:8080/actuator in a browser; the JSON lists every exposed endpoint with its URL.
  4. Call each endpoint with curl and paste the responses into the record.

Configuration

The dependency block, for reference:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.properties (replaces)properties
# src/main/resources/application.properties  (Session 8: Session 7 settings + Actuator, Q33)
spring.application.name=admission-api
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/admission_db?createDatabaseIfNotExist=true&serverTimezone=Asia/Kolkata
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.defer-datasource-initialization=true
spring.sql.init.mode=always

# --- Actuator (Q33) ---
# By default only /actuator/health is exposed over HTTP. Expose every endpoint for the lab.
management.endpoints.web.exposure.include=*
# show the db and diskSpace components, not just {"status":"UP"}
management.endpoint.health.show-details=always
# allow shutdown via POST /actuator/shutdown (disabled by default)
management.endpoint.shutdown.enabled=true
# /actuator/info reads info.* properties only when env info is enabled
management.info.env.enabled=true
info.app.name=admission-api
info.app.course=MCSL-222 Web Technologies Lab
info.app.version=0.0.1

Output

Checked by reading. The discovery page lists the endpoints this project exposes (Boot 3.3, no Prometheus, no Flyway, no logging.file):

curl -s http://localhost:8080/actuator
{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},
"beans":{"href":"http://localhost:8080/actuator/beans","templated":false},
"health":{"href":"http://localhost:8080/actuator/health","templated":false},
"health-path":{"href":"http://localhost:8080/actuator/health/{*path}","templated":true},
"info":{"href":"http://localhost:8080/actuator/info","templated":false},
"conditions":{"href":"http://localhost:8080/actuator/conditions","templated":false},
"configprops":{"href":"http://localhost:8080/actuator/configprops","templated":false},
"env":{"href":"http://localhost:8080/actuator/env","templated":false},
"loggers":{"href":"http://localhost:8080/actuator/loggers","templated":false},
"heapdump":{"href":"http://localhost:8080/actuator/heapdump","templated":false},
"threaddump":{"href":"http://localhost:8080/actuator/threaddump","templated":false},
"metrics":{"href":"http://localhost:8080/actuator/metrics","templated":false},
"sbom":{"href":"http://localhost:8080/actuator/sbom","templated":false},
"scheduledtasks":{"href":"http://localhost:8080/actuator/scheduledtasks","templated":false},
"mappings":{"href":"http://localhost:8080/actuator/mappings","templated":false},
"shutdown":{"href":"http://localhost:8080/actuator/shutdown","templated":false}}}

Each endpoint, what it returns, and the command used:

EndpointCommandExpected response (shortened)
healthcurl -s localhost:8080/actuator/health"status":"UP" with db (MySQL, isValid()), diskSpace (total, free, threshold) and ping components
infocurl -s localhost:8080/actuator/info"app": with name, course and version from the info.* properties
metricscurl -s localhost:8080/actuator/metrics"names":[...] about 60 metric names: jvm.memory.used, http.server.requests, hikaricp.connections.active, system.cpu.usage, …
metrics by namecurl -s localhost:8080/actuator/metrics/jvm.memory.used"measurements":[ with "statistic":"VALUE","value":1.2e8 and availableTags for area and id
envcurl -s localhost:8080/actuator/env/server.port"property": with "value":"8080" and source Config resource 'class path resource [application.properties]'
beanscurl -s localhost:8080/actuator/beansEvery bean in the context with its scope, type and dependencies, including studentRestController and studentRepository
mappingscurl -s localhost:8080/actuator/mappingsdispatcherServlets with a handler entry per method, for example GET /api/students/[id]
loggerscurl -s localhost:8080/actuator/loggers/in.ignou"configuredLevel":null,"effectiveLevel":"INFO"
configpropscurl -s localhost:8080/actuator/configpropsBound @ConfigurationProperties such as spring.datasource (password masked as ******)
conditionscurl -s localhost:8080/actuator/conditionspositiveMatches and negativeMatches of every auto-configuration
threaddumpcurl -s localhost:8080/actuator/threaddumpArray of threads with name, state and stack
heapdumpcurl -o heap.hprof localhost:8080/actuator/heapdumpBinary .hprof file
scheduledtasks, sbomcurl -s localhost:8080/actuator/scheduledtasksEmpty lists (nothing scheduled; no SBOM file in the jar). caches is absent because no CacheManager bean exists
shutdowncurl -s -X POST localhost:8080/actuator/shutdown"message":"Shutting down, bye..." and the process exits

Two full responses worth copying:

curl -s http://localhost:8080/actuator/health
{"status":"UP","components":{
 "db":{"status":"UP","details":{"database":"MySQL","validationQuery":"isValid()"}},
 "diskSpace":{"status":"UP","details":{"total":499963174912,"free":201524125696,"threshold":10485760,"path":"/home/mca/admission-api/.","exists":true}},
 "ping":{"status":"UP"}}}
curl -s http://localhost:8080/actuator/info
{"app":{"name":"admission-api","course":"MCSL-222 Web Technologies Lab","version":"0.0.1"}}

Change a log level without restarting:

curl -s -X POST -H "Content-Type: application/json" -d '{"configuredLevel":"DEBUG"}' \
  http://localhost:8080/actuator/loggers/in.ignou.admission

Response: HTTP 204, then the console starts printing DEBUG lines from the project package.

Explanation

Actuator registers each endpoint as a bean; the management.endpoints.web.exposure.include=* line maps all of them under /actuator. Without that line only health is reachable over HTTP, which is the safe default because env, beans and heapdump leak configuration and memory. show-details=always makes health include the db component, which runs Connection.isValid() on a pooled connection; that is the check operations teams poll. info is empty in Boot 3 until management.info.env.enabled=true and some info.* keys exist. shutdown is the only endpoint disabled by default; it needs management.endpoint.shutdown.enabled=true and a POST. After Session 9 the whole /actuator tree sits behind login except /actuator/health.

Question 34

Problem Statement

Write in lab record

Create REST API for CRUD operation using Spring Boot and Hibernate/JPA using annotation.

Solution

Write in lab record

Steps

  1. Replace StudentRestController.java from Session 7 with the listing below; it keeps the two GET methods and adds POST, PUT and DELETE.
  2. Restart with DevTools (saving the file is enough).
  3. Run the five curl calls in order; the ids in the later calls come from the POST response.
  4. Verify in MySQL after each step: SELECT * FROM student;.

Program

StudentRestController.java (replaces)java
// src/main/java/in/ignou/admission/web/StudentRestController.java   (Session 8, Q34: full CRUD)
package in.ignou.admission.web;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import in.ignou.admission.entity.Student;
import in.ignou.admission.repository.StudentRepository;

/**
 * Annotation-based REST CRUD.
 *   POST   /api/students        create  -> 201 + saved student
 *   GET    /api/students        read    -> 200 + list
 *   GET    /api/students/{id}   read    -> 200 or 404
 *   PUT    /api/students/{id}   update  -> 200 or 404
 *   DELETE /api/students/{id}   delete  -> 204 or 404
 */
@RestController
@RequestMapping("/api/students")
public class StudentRestController {

    private final StudentRepository students;

    public StudentRestController(StudentRepository students) {
        this.students = students;
    }

    /** @RequestBody: Jackson turns the JSON body into a Student; id is ignored and generated. */
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Student create(@RequestBody Student student) {
        student.setId(null);
        return students.save(student);
    }

    @GetMapping
    public List<Student> all(@RequestParam(required = false) String city) {
        return city == null ? students.findAll() : students.findByCityIgnoreCase(city);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Student> one(@PathVariable Long id) {
        return students.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    /** Copy the editable fields onto the managed entity, then save (an UPDATE, because id is set). */
    @PutMapping("/{id}")
    public ResponseEntity<Student> update(@PathVariable Long id, @RequestBody Student in) {
        return students.findById(id).map(s -> {
            s.setName(in.getName());
            s.setEmail(in.getEmail());
            s.setPhone(in.getPhone());
            s.setCity(in.getCity());
            s.setDateOfBirth(in.getDateOfBirth());
            return ResponseEntity.ok(students.save(s));
        }).orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        if (!students.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        students.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}

Output

Checked by reading. Create:

curl -i -X POST http://localhost:8080/api/students \
  -H "Content-Type: application/json" \
  -d '{"name":"Meera Nair","email":"meera@example.com","phone":"9000011111","city":"Kochi","dateOfBirth":"2003-01-25"}'
HTTP/1.1 201
Content-Type: application/json

{"id":3,"name":"Meera Nair","email":"meera@example.com","phone":"9000011111","city":"Kochi","dateOfBirth":"2003-01-25"}

Read all and read one:

curl -s http://localhost:8080/api/students
curl -s http://localhost:8080/api/students/3
[{"id":1,"name":"Asha Verma",...},{"id":2,"name":"Ravi Kumar",...},{"id":3,"name":"Meera Nair",...}]
{"id":3,"name":"Meera Nair","email":"meera@example.com","phone":"9000011111","city":"Kochi","dateOfBirth":"2003-01-25"}

Update (full replacement of the editable fields):

curl -s -X PUT http://localhost:8080/api/students/3 \
  -H "Content-Type: application/json" \
  -d '{"name":"Meera Nair","email":"meera.nair@example.com","phone":"9000011111","city":"Thrissur","dateOfBirth":"2003-01-25"}'
{"id":3,"name":"Meera Nair","email":"meera.nair@example.com","phone":"9000011111","city":"Thrissur","dateOfBirth":"2003-01-25"}

Delete, then read again:

curl -i -X DELETE http://localhost:8080/api/students/3
curl -i http://localhost:8080/api/students/3
HTTP/1.1 204

HTTP/1.1 404

Error cases: a duplicate email on POST violates the unique key and returns HTTP 500 with DataIntegrityViolationException in the console; a body that is not JSON returns HTTP 400 (HttpMessageNotReadableException).

Explanation

Each HTTP verb has its own annotation, all shortcuts for @RequestMapping(method = ...). @RequestBody asks Jackson to build a Student from the request JSON; student.setId(null) guarantees an INSERT even if the client sent an id. @ResponseStatus(CREATED) sets 201 for the create path where the body is always present. PUT loads the managed entity, copies the fields, and calls save; because the entity has an id, Hibernate issues an UPDATE. DELETE checks existsById first so a missing id is 404 rather than a silent 204. Every method goes through the same JpaRepository, so the controller has no SQL and no session handling of its own; Boot’s OpenEntityManagerInViewInterceptor and the repository’s @Transactional methods do that.

Question 35

Problem Statement

Write in lab record

Create a REST API for CRUD operation using Spring Boot and Hibernate/JPA using XML configuration.

Solution

Write in lab record

Steps

  1. Create a new package in.ignou.xmlapi beside (not inside) in.ignou.admission. Component scanning does not reach it, so nothing in it becomes a bean unless XML says so.
  2. Add CourseService (a plain class) and CourseRestController to that package.
  3. Create src/main/resources/beans.xml with the two <bean> definitions.
  4. Replace AdmissionApiApplication.java with the version that carries @ImportResource("classpath:beans.xml").
  5. Restart and test /xml/courses with the curl commands.
  6. Prove the wiring is XML: comment out the courseRestController bean in beans.xml, restart, and GET /xml/courses returns 404.

Program

Lab record: every tab is one file of the answer. Write all of them.

CourseService.javajava
// src/main/java/in/ignou/xmlapi/CourseService.java   (Session 8, Q35)
// NOTE the package: in.ignou.xmlapi is OUTSIDE in.ignou.admission, so component scanning
// never sees these classes. Only beans.xml creates them.
package in.ignou.xmlapi;

import java.util.List;
import java.util.Optional;

import in.ignou.admission.entity.Course;
import in.ignou.admission.repository.CourseRepository;

/** Plain class: no @Service, no @Autowired. beans.xml supplies the repository through the constructor. */
public class CourseService {

    private final CourseRepository courses;

    public CourseService(CourseRepository courses) {
        this.courses = courses;
    }

    public Course create(Course c) {
        c.setId(null);
        return courses.save(c);
    }

    public List<Course> all() {
        return courses.findAll();
    }

    public Optional<Course> find(Long id) {
        return courses.findById(id);
    }

    public Optional<Course> update(Long id, Course in) {
        return courses.findById(id).map(c -> {
            c.setCode(in.getCode());
            c.setTitle(in.getTitle());
            c.setCredits(in.getCredits());
            c.setProgramme(in.getProgramme());
            return courses.save(c);
        });
    }

    public boolean delete(Long id) {
        if (!courses.existsById(id)) {
            return false;
        }
        courses.deleteById(id);
        return true;
    }
}
CourseRestController.javajava
// src/main/java/in/ignou/xmlapi/CourseRestController.java   (Session 8, Q35)
package in.ignou.xmlapi;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import in.ignou.admission.entity.Course;

/**
 * The bean is declared in beans.xml, not found by scanning. @RestController stays,
 * because Spring MVC 6 only treats @Controller-annotated beans as request handlers.
 */
@RestController
@RequestMapping("/xml/courses")
public class CourseRestController {

    private final CourseService service;

    public CourseRestController(CourseService service) {
        this.service = service;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Course create(@RequestBody Course course) {
        return service.create(course);
    }

    @GetMapping
    public List<Course> all() {
        return service.all();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Course> one(@PathVariable Long id) {
        return service.find(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @PutMapping("/{id}")
    public ResponseEntity<Course> update(@PathVariable Long id, @RequestBody Course course) {
        return service.update(id, course).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        return service.delete(id) ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build();
    }
}
beans.xmlxml
<?xml version="1.0" encoding="UTF-8"?>
<!-- src/main/resources/beans.xml : XML wiring for the Course CRUD API (Session 8, Q35) -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- courseRepository already exists: Spring Data registered it from the interface name.
       Constructor injection by reference, same as @Autowired would do. -->
  <bean id="courseService" class="in.ignou.xmlapi.CourseService">
    <constructor-arg ref="courseRepository"/>
  </bean>

  <bean id="courseRestController" class="in.ignou.xmlapi.CourseRestController">
    <constructor-arg ref="courseService"/>
  </bean>

</beans>
AdmissionApiApplication.java (replaces)java
// src/main/java/in/ignou/admission/AdmissionApiApplication.java   (Session 8: + @ImportResource)
package in.ignou.admission;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource("classpath:beans.xml")   // Q35: load the XML-defined beans into the same context
public class AdmissionApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(AdmissionApiApplication.class, args);
    }
}

Output

Checked by reading. A course carries its programme, so the JSON nests it:

curl -s http://localhost:8080/xml/courses
[{"id":1,"code":"MCS-221","title":"Data Warehousing and Data Mining","credits":4,"programme":{"id":1,"code":"MCA","name":"Master of Computer Applications","durationYears":2}},
 {"id":2,"code":"MCSL-222","title":"Web Technologies Lab","credits":2,"programme":{"id":1,"code":"MCA","name":"Master of Computer Applications","durationYears":2}},
 {"id":3,"code":"BCS-011","title":"Computer Basics and PC Software","credits":3,"programme":{"id":2,"code":"BCA","name":"Bachelor of Computer Applications","durationYears":3}}]

Create (only the programme id is needed; Hibernate resolves the reference):

curl -i -X POST http://localhost:8080/xml/courses -H "Content-Type: application/json" \
  -d '{"code":"MCS-224","title":"Artificial Intelligence and Machine Learning","credits":4,"programme":{"id":1}}'
HTTP/1.1 201

{"id":4,"code":"MCS-224","title":"Artificial Intelligence and Machine Learning","credits":4,"programme":{"id":1,"code":null,"name":null,"durationYears":0}}

The nested programme shows nulls in the POST response because the request only carried the id; GET /xml/courses/4 right after returns the full programme.

Read one, update, delete:

curl -s http://localhost:8080/xml/courses/4
curl -s -X PUT http://localhost:8080/xml/courses/4 -H "Content-Type: application/json" \
  -d '{"code":"MCS-224","title":"AI and Machine Learning","credits":4,"programme":{"id":1}}'
curl -i -X DELETE http://localhost:8080/xml/courses/4
{"id":4,"code":"MCS-224","title":"Artificial Intelligence and Machine Learning","credits":4,"programme":{"id":1,"code":"MCA","name":"Master of Computer Applications","durationYears":2}}
{"id":4,"code":"MCS-224","title":"AI and Machine Learning","credits":4,"programme":{"id":1,"code":null,"name":null,"durationYears":0}}
HTTP/1.1 204

Start-up evidence that the XML was read:

Loading XML bean definitions from class path resource [beans.xml]

Explanation

Annotation configuration and XML configuration produce the same thing, bean definitions in one ApplicationContext. @ImportResource reads beans.xml into the Boot context, so an XML bean can reference courseRepository, which Spring Data registered from the interface. Constructor injection in XML is <constructor-arg ref="..."/>; the classes have no @Service, @Component or @Autowired. The controller keeps @RestController and @GetMapping for one reason: since Spring 6, RequestMappingHandlerMapping accepts a bean as a handler only when its class is annotated with @Controller, and the URL-to-method table can only be declared with mapping annotations. What XML controls is the bean’s existence and its dependencies, which is exactly why step 6 makes the endpoint disappear. Putting the classes in in.ignou.xmlapi avoids a BeanDefinitionOverrideException: if they sat under in.ignou.admission, scanning and XML would both try to register courseRestController.

Question 36

Problem Statement

Write in lab record

Add Spring Security dependency in the existing spring boot application created in Exercise 2 of Session 8 and configure it as the default login.

Solution

Write in lab record

Steps

  1. Paste the dependency below into pom.xml and run Maven, Update Project.
  2. Restart. Copy the generated password from the console line Using generated security password:.
  3. Open http://localhost:8080/api/students in the browser. Spring redirects to /login, its built-in page with Username, Password and a Sign in button.
  4. Log in as user with the generated password; the browser is redirected back to /api/students and the JSON appears.
  5. Test from curl with HTTP Basic (-u user:password).
  6. Optional: pin the credentials in application.properties so they survive restarts (spring.security.user.name=admin, spring.security.user.password=admin123).

Configuration

pom.xml fragmentxml
<!-- Session 8, Q36: add inside <dependencies> of pom.xml, then Maven > Update Project -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Output

Checked by reading. Console at start-up (the UUID changes every run):

Using generated security password: 6a1b7f5c-9d2e-4c3a-8b0f-1e2d3c4b5a69

This generated password is for development use only. Your security configuration must be updated before running your application in production.

Browser: any URL now shows Spring’s default login page, a centred form titled “Please sign in” with Username, Password and a blue “Sign in” button. A wrong password shows “Bad credentials” above the form. After login, the original URL loads.

curl without credentials, then with:

curl -i http://localhost:8080/api/students
HTTP/1.1 401
WWW-Authenticate: Basic realm="Realm"
Set-Cookie: JSESSIONID=...; Path=/; HttpOnly
curl -s -u user:6a1b7f5c-9d2e-4c3a-8b0f-1e2d3c4b5a69 http://localhost:8080/api/students
[{"id":1,"name":"Asha Verma",...},{"id":2,"name":"Ravi Kumar",...}]

Writes are blocked even with the right password, because the default chain also turns on CSRF:

curl -i -u user:6a1b7f5c-9d2e-4c3a-8b0f-1e2d3c4b5a69 -X DELETE http://localhost:8080/api/students/2
HTTP/1.1 403

Session 9 fixes that by excluding /api/** from CSRF in the custom SecurityFilterChain.

Explanation

Adding the starter triggers SecurityAutoConfiguration. With no SecurityFilterChain bean of our own, Boot installs its default: every request must be authenticated, form login at /login and HTTP Basic are both enabled, CSRF protection is on, and an InMemoryUserDetailsManager holds one user named user with a random UUID password printed once. Actuator endpoints are covered too, which is why /actuator/health also asks for a login now. The WWW-Authenticate: Basic header on the 401 is what lets curl’s -u work; browsers get a 302 to /login instead because Spring detects the Accept: text/html header. The point of the exercise is to see the whole surface locked down with zero code; Session 9 replaces each default with a database-backed one.

Viva Questions

Do not copy. Read for understanding and the viva
  • Q: Which Actuator endpoint is exposed over HTTP by default and why only that one? A: health; the others (env, beans, heapdump) reveal configuration and memory, so they are opt-in.
  • Q: What does management.endpoint.health.show-details=always change? A: The response includes the db, diskSpace and ping components instead of a bare status.
  • Q: Which HTTP status should POST, DELETE and a missing id return? A: 201 Created, 204 No Content, 404 Not Found.
  • Q: How does save decide between INSERT and UPDATE? A: If the entity id is null it persists (INSERT); otherwise it merges (UPDATE).
  • Q: Why does the XML-configured controller still carry @RestController? A: Spring MVC 6 only treats @Controller-annotated beans as handlers; XML supplies the bean and its dependencies, not the URL mappings.
  • Q: What would happen if CourseService were also annotated @Service inside the scanned package? A: Two definitions of the same bean name; Boot refuses to start with BeanDefinitionOverrideException.
  • Q: Where does the generated security password come from? A: UserDetailsServiceAutoConfiguration creates an in-memory user user with a random UUID when no UserDetailsService bean exists.
  • Q: Why does curl get 401 but the browser gets a redirect to /login? A: The default chain enables both HTTP Basic and form login and picks the response by the request’s Accept header.

Common Mistakes

Do not copy. Read for understanding and the viva
  • Testing /actuator/metrics without the exposure property and reporting “Actuator is broken” when the 404 only means the endpoint is not exposed.
  • Sending JSON without Content-Type: application/json; Spring answers 415 Unsupported Media Type.
  • Treating PUT as a partial update; a body with a missing field overwrites that column with null.
  • Placing the XML-wired classes inside in.ignou.admission and getting duplicate-bean errors, or forgetting @ImportResource and getting 404 on /xml/courses.
  • Copying the generated password from an old console; it changes on every restart.
  • Expecting DELETE through curl to work right after adding the security starter; CSRF blocks it until the chain is customised.

Session Summary

Write in lab record
  • application.properties with Actuator exposure, health details and info.* keys, plus the /actuator discovery JSON and the endpoint table with one curl per endpoint
  • StudentRestController with POST, GET, PUT and DELETE and the five curl transcripts (201, 200, 200, 204, 404)
  • CourseService, CourseRestController, beans.xml and the @ImportResource application class, with the /xml/courses transcripts
  • The security dependency, the generated-password console line, the default login page description and the 401/403 curl results
Navigation

Type to search…

↑↓ navigate↵ selectEsc close