Sunday, December 14, 2025

Java spring notes12

 Handson

1. Create spring boot appl to insert, update employee, fetch all empl and fetch empl by id

  Employee class - id, name, age, address, dept, salary

   Implement exception handling and validation along with inmemory authentication

 

 

1. If we provide some wrong info then it has to throw some exception, so we have to handle the exception globally for that we have to use @RestControllerAdvice

 

public class MovieAlreadyExistsException extends RuntimeException{

    private String message;

 

              public MovieAlreadyExistsException() {

                             super();

                             // TODO Auto-generated constructor stub

              }

 

              public MovieAlreadyExistsException(String message) {

                             super(message);

                             this.message = message;

              }

}

 

public class MovieNotFoundException extends RuntimeException {

   private String message;

 

public MovieNotFoundException() {

              super();

              // TODO Auto-generated constructor stub

}

 

public MovieNotFoundException(String message) {

              super(message);

              this.message=message;

}

  

   

}

 

 

@Service

public class MovieService {

 

              @Autowired

              MovieRepository movieRepo;

             

              public Movie createMovie(Movie movie) {

                             if(movieRepo.existsById(movie.getId())) {

                                           throw new MovieAlreadyExistsException("Movie Already exists!!!");

                             }

                             return movieRepo.save(movie);

              }

 

              public List<Movie> getAllMovies() {

                             return movieRepo.findAll();

              }

 

              public Movie getMovieById(Integer mid) {

                             return movieRepo.findById(mid).orElseThrow(()-> new MovieNotFoundException("Movie with id ="+mid+" does not found"));

              }

}

 

 

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ErrorResponse {

   private int statusCode;

   private String message;

}

 

@RestControllerAdvice

public class GlobalExceptionHandler {

 

              @ExceptionHandler(value = MovieAlreadyExistsException.class)

              @ResponseStatus(HttpStatus.CONFLICT)

              public @ResponseBody ErrorResponse handleMovieAlreadyExists(MovieAlreadyExistsException m) {

                             return new ErrorResponse(HttpStatus.CONFLICT.value(), m.getMessage());

              }

             

              @ExceptionHandler(value = MovieNotFoundException.class)

              @ResponseStatus(HttpStatus.BAD_REQUEST)

              public @ResponseBody ErrorResponse handleMovieNotFound(MovieNotFoundException m) {

                             return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), m.getMessage());

              }

}

 

- Start the appl

 

From Springboot3.x onwards we have ProblemDetail class which allows us to add those details about error and display error details for the client in a standardised format

 

@RestControllerAdvice

public class GlobalExceptionHandler {

 

              /*@ExceptionHandler(value = MovieAlreadyExistsException.class)

              @ResponseStatus(HttpStatus.CONFLICT)

              public @ResponseBody ErrorResponse handleMovieAlreadyExists(MovieAlreadyExistsException m) {

                             return new ErrorResponse(HttpStatus.CONFLICT.value(), m.getMessage());

              }

             

              @ExceptionHandler(value = MovieNotFoundException.class)

              @ResponseStatus(HttpStatus.BAD_REQUEST)

              public @ResponseBody ErrorResponse handleMovieNotFound(MovieNotFoundException m) {

                             return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), m.getMessage());

              }*/

             

              @ExceptionHandler(value = MovieAlreadyExistsException.class)

              @ResponseStatus(HttpStatus.CONFLICT)

              public ProblemDetail handleMovieAlreadyExists(MovieAlreadyExistsException m) throws Exception {

                             ProblemDetail p=ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, m.getMessage());

                             p.setProperty("host", "localhost");

                             p.setProperty("port", 5000);

                             p.setType(new URI(http://localhost:5000/movie/movie-already-exists));

                             p.setTitle("Movie Already Exists");

                             p.setDetail("Movie already exists since we are storing movie with same id");

                             p.setStatus(HttpStatus.CONFLICT);

                             return p;

              }

             

              @ExceptionHandler(value = MovieNotFoundException.class)

              @ResponseStatus(HttpStatus.BAD_REQUEST)

              public ProblemDetail handleMovieNotFound(MovieNotFoundException m) throws Exception {

                             ProblemDetail p=ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, m.getMessage());

                             p.setProperty("host", "localhost");

                             p.setProperty("port", 5000);

                             p.setType(new URI(http://localhost:5000/movie/movie-not-found));

                             p.setTitle("Movie not found");

                             p.setDetail("Movie not found since we are accessing movie with some id");

                             p.setStatus(HttpStatus.BAD_REQUEST);

                             return p;

              }

}

 

2. Springboot Input Validation

       - We have to provide spring-boot-starter-validation dependency

 

<dependency>

                                           <groupId>org.springframework.boot</groupId>

                                           <artifactId>spring-boot-starter-validation</artifactId>

                             </dependency>

 

- Provide validation annotations in entity class

 

@Entity

@Table(name="movie100")

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Movie {

   @Id

   private Integer id;

   @NotNull(message="Movie name cannot be null")

   private String name;

   @NotNull(message="Movie language cannot be null")

   private String language;

   @NotNull(message="Movie type cannot be null")

   private String type;

   @Min(value=1,message="Rating minimum value should be 1")

   @Max(value=5,message="Rating maximum value should be 5")

   private Integer rating;

}

 

@NotNull

@Size(min=4,max=8,message="Password should be between 4 to 8 chars")

private String password;

 

@Length(min=16,max=16)

@Pattern(regextp="^([0-9][1-9]*{16}$");

private Integer creditCard;

 

@Digit(integer=3,fraction=0,message="Max 3 digits aLlowed for CVV")

private Integer cvv;

 

@Past(message="DOB should be only past date")

private Date dob;

 

@Future(message="DOJ must be only future date")

private date doe;

 

 

@NotNull - check only for null values

{

  "id": 101,

  "name": "Friends1",

  "type": "Comedy"

}

 

 

@NotEmpty - check only for empty values

{

  "id": 101,

  "name": "Friends1",

  "langugae":""

  "type": "Comedy"

  "rating": 5

}

 

 

@NotBlank- check only for blank values

{

  "id": 101,

  "name": "Friends1",

  "langugae":"     "

  "type": "Comedy"

  "rating": 5

}

 

- Provide @Validated annotation where we pass input to the controler prg   

 

 

              @PostMapping("/movie")

              public ResponseEntity<Movie> createMovie(@Validated @RequestBody Movie movie) {

                             Movie savedMovie=movieService.createMovie(movie);

                             return new ResponseEntity<Movie>(savedMovie,HttpStatus.CREATED);

             

- Start the appl, if we provide some wrong input value then it will throw an exception MethodArgumentNotValidException,so we have to configure this exception in GlobalExceptionHandler

 

 

@ExceptionHandler(value = MethodArgumentNotValidException.class)

              @ResponseStatus(HttpStatus.BAD_REQUEST)

              public Map<String,String> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {

                             Map<String,String> errors=new HashMap<>();

                             List<ObjectError> allErrors=e.getBindingResult().getAllErrors();

                             allErrors.forEach(err -> {

                                           FieldError fe=(FieldError)err;

                                           errors.put(fe.getField(), fe.getDefaultMessage());

                             });

                             return errors;

              }

 

 

Spring Security

   - used to secure ur endpoints, who has privilege to access which endpoints

 

 

1. Add Spring security dependency (ie) spring-boot-starter-security, since we added security dependency by default security is enabled in ur appl

 

2. Start the appl, it will be provided with default password, run the appl http://localhost:5000/api/

  - It will by default open login page provided by spring security

       username: user

       password: paste generated pwd

 

3. Inorder to use our own username, pwd, we have to configure username and pwd in application.properties

 

spring.security.user.name=Ram

spring.security.user.password=abcd

 

4. Start the appl, run http://localhost:5000/api/, it will by default open login page where we have to provide username and pwd configured in application.properties

 

But this approach is not recommended approach, since we have hardcoded only one username and pwd in application.properties

 

5. If we want to configure multiple username and pwd with different roles, then we have to create separate class with @Configuration and @EnableWebSecurity annotation where we provide the logic for authentication and authorization

    Before Springboot3.0 version we have WebSecurityConfigureAdapter class which contains 2 overloaded  configure() method

1. configure(AuthenticationManagerBuilder a) which used for authentication purpose (ie) validate username and pwd

2. configure(HttpSecurity h) which used for authorization (ie) we provide privilege to user so that which user can access which endpoint

    But from Springboot3.0, this WebSecurityConfigureAdapter class is completely removed, then to perform authentication and authorization we have

1. UserDetailsService interface - used for authentication - 3 ways

a. In-memory authentication - we manually configure username and pwd along with their roles

b. Database authentication - fetch username and pwd from db

c. JWT Authentication

 

2. SecurityFilterChain interface - used for authorization

 

@Configuration

@EnableWebSecurity

public class SecurityConfig {

 

              //Authentication

              @Bean 

              public UserDetailsService userDetailsService(PasswordEncoder encoder) {

                             //In-Memory Authentication

                             UserDetails admin=User.withUsername("Ram")

                                                                        .password(encoder.encode("abcd"))

                                                                        .roles("ADMIN").build();

                             UserDetails user=User.withUsername("Sam")

                            .password(encoder.encode("xyz"))

                            .roles("USER").build();

                             return new InMemoryUserDetailsManager(admin,user);

              }

             

              @Bean

              public PasswordEncoder passwordEncoder() {

                             return new BCryptPasswordEncoder();

              }

             

             

              //Authorization

              @Bean

              public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{

                             return http.csrf(csrf->csrf.disable())

                                                             .authorizeHttpRequests(auth -> auth.requestMatchers("/api/welcome").permitAll()

                                                                                                   .anyRequest().authenticated())

                                                             .formLogin().and().build();

              }

}

 

6. Comment username and pwd in application.properties

 

7. Add mysql dependency in pom.xml

 

8. Start the appl and add movie info in table

 

http://localhost:5000/api/welcome - we can access without username and pwd

 

http://localhost:5000/api/movie - we can access only with username and pwd

 

http://localhost:5000/api/movie/100 - we can access only with username and pwd

 

9. Now both Ram and Sam can access all endpoints eventhough we have provided roles, now we want to restrict endpoints based on their roles for that we define @PreAuthorize annotation

 

@RestController

@RequestMapping("/api")

public class MovieController {

             

              @Autowired

              MovieService movieService;

 

              @GetMapping("/welcome")

              public String getMovieInfo() {

                             return "Welcome to Movie Application";

              }

             

              @PostMapping("/movie")

              @PreAuthorize("hasAuthority('ROLE_ADMIN')")

              public ResponseEntity<Movie> createMovie(@Validated @RequestBody Movie movie) {

                             Movie savedMovie=movieService.createMovie(movie);

                             return new ResponseEntity<Movie>(savedMovie,HttpStatus.CREATED);

              }

             

              @GetMapping("/movie")

              @PreAuthorize("hasAuthority('ROLE_ADMIN')")

              public ResponseEntity<List<Movie>> getAllMovies() {

                             List<Movie> movieList = movieService.getAllMovies();

                             if(movieList.isEmpty())

                                           return new ResponseEntity<>(HttpStatus.NO_CONTENT);

                             return new ResponseEntity<>(movieList,HttpStatus.CREATED);

              }

             

              @GetMapping("/movie/{id}")

              @PreAuthorize("hasAuthority('ROLE_USER')")

              public ResponseEntity<Movie> getMovieById(@PathVariable("id") Integer mid) {

                             Movie movie=movieService.getMovieById(mid);

                             return new ResponseEntity<>(movie,HttpStatus.CREATED);

              }

}

 

- By defining @PreAuthorize we cannot achieve authorization, we have to inform that we have implemented method level authorization using @EnableMethodSecurity in SecurityConfig class

 

@Configuration

@EnableWebSecurity

@EnableMethodSecurity

public class SecurityConfig {

 

}

 

10. Start the appl

 

http://localhost:5000/api/welcome - everyone can access without username and pwd

 

http://localhost:5000/api/movie - only Ram can access with username and pwd

 

http://localhost:5000/api/movie/100 - only Sam can access  with username and pwd

No comments:

Post a Comment