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