From 573e0e1db330c84fe9d363b08ce205b9dcb5c634 Mon Sep 17 00:00:00 2001 From: Alisson Youssf Date: Sat, 6 Jan 2024 20:20:24 -0300 Subject: [PATCH 1/4] USER-ROLE Model orm, seeding --- .../controllers/DepartmentController.java | 11 +- .../bds03/controllers/EmployeeController.java | 11 +- .../devsuperior/bds03/dto/DepartmentDTO.java | 4 +- .../bds03/entities/Department.java | 6 +- .../devsuperior/bds03/entities/Employee.java | 8 +- .../com/devsuperior/bds03/entities/Role.java | 55 ++++++++ .../com/devsuperior/bds03/entities/User.java | 118 ++++++++++++++++++ .../bds03/services/DepartmentService.java | 25 ++-- .../bds03/services/EmployeeService.java | 21 ++-- src/main/resources/data.sql | 10 ++ 10 files changed, 230 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/devsuperior/bds03/entities/Role.java create mode 100644 src/main/java/com/devsuperior/bds03/entities/User.java diff --git a/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java b/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java index 9af99206..c0f34b2a 100644 --- a/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java +++ b/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java @@ -15,10 +15,13 @@ @RequestMapping(value = "/departments") public class DepartmentController { - @Autowired - private DepartmentService service; - - @GetMapping + private final DepartmentService service; + + public DepartmentController(DepartmentService service) { + this.service = service; + } + + @GetMapping public ResponseEntity> findAll() { List list = service.findAll(); return ResponseEntity.ok().body(list); diff --git a/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java b/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java index 4969d1c4..73cbb6c1 100644 --- a/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java +++ b/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java @@ -24,10 +24,13 @@ @RequestMapping(value = "/employees") public class EmployeeController { - @Autowired - private EmployeeService service; - - @GetMapping + private final EmployeeService service; + + public EmployeeController(EmployeeService service) { + this.service = service; + } + + @GetMapping public ResponseEntity> findAll(Pageable pageable) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by("name")); Page list = service.findAll(pageRequest); diff --git a/src/main/java/com/devsuperior/bds03/dto/DepartmentDTO.java b/src/main/java/com/devsuperior/bds03/dto/DepartmentDTO.java index 4b0f7d44..56c36f65 100644 --- a/src/main/java/com/devsuperior/bds03/dto/DepartmentDTO.java +++ b/src/main/java/com/devsuperior/bds03/dto/DepartmentDTO.java @@ -7,8 +7,8 @@ public class DepartmentDTO implements Serializable { private static final long serialVersionUID = 1L; - public Long id; - public String name; + private Long id; + private String name; public DepartmentDTO() { } diff --git a/src/main/java/com/devsuperior/bds03/entities/Department.java b/src/main/java/com/devsuperior/bds03/entities/Department.java index fdfa6437..0f56bd05 100644 --- a/src/main/java/com/devsuperior/bds03/entities/Department.java +++ b/src/main/java/com/devsuperior/bds03/entities/Department.java @@ -16,11 +16,11 @@ public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long id; - public String name; + private Long id; + private String name; @OneToMany(mappedBy = "department") - public List employees = new ArrayList<>(); + private final List employees = new ArrayList<>(); public Department() { } diff --git a/src/main/java/com/devsuperior/bds03/entities/Employee.java b/src/main/java/com/devsuperior/bds03/entities/Employee.java index 03ea4f48..119e2c8a 100644 --- a/src/main/java/com/devsuperior/bds03/entities/Employee.java +++ b/src/main/java/com/devsuperior/bds03/entities/Employee.java @@ -14,13 +14,13 @@ public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long id; - public String name; - public String email; + private Long id; + private String name; + private String email; @ManyToOne @JoinColumn(name = "department_id") - public Department department; + private Department department; public Employee() { } diff --git a/src/main/java/com/devsuperior/bds03/entities/Role.java b/src/main/java/com/devsuperior/bds03/entities/Role.java new file mode 100644 index 00000000..2cb71c49 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/entities/Role.java @@ -0,0 +1,55 @@ +package com.devsuperior.bds03.entities; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "tb_role") +public class Role implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String authority; + + public Role() { + } + + public Role(Long id , String authority) { + this.id = id; + this.authority = authority; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Role)) return false; + + Role role = (Role) o; + + return Objects.equals(id , role.id); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/src/main/java/com/devsuperior/bds03/entities/User.java b/src/main/java/com/devsuperior/bds03/entities/User.java new file mode 100644 index 00000000..eb0e519f --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/entities/User.java @@ -0,0 +1,118 @@ +package com.devsuperior.bds03.entities; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Entity +@Table(name = "tb_user") +public class User implements UserDetails, Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + + @Column(unique = true) + private String email; + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "tb_user_role", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private final Set roles = new HashSet<>(); + + public User() { + } + + public User(Long id , String email , String password) { + this.id = id; + this.email = email; + this.password = password; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRoles() { + return roles; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof User)) return false; + + User user = (User) o; + + return Objects.equals(id , user.id); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + + @Override + public Collection getAuthorities() { + return roles.stream() + .map(role -> new SimpleGrantedAuthority(role.getAuthority())) + .collect(Collectors.toList()); + } + + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/devsuperior/bds03/services/DepartmentService.java b/src/main/java/com/devsuperior/bds03/services/DepartmentService.java index 326007d7..a438e1f3 100644 --- a/src/main/java/com/devsuperior/bds03/services/DepartmentService.java +++ b/src/main/java/com/devsuperior/bds03/services/DepartmentService.java @@ -1,24 +1,25 @@ package com.devsuperior.bds03.services; -import java.util.List; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; - import com.devsuperior.bds03.dto.DepartmentDTO; import com.devsuperior.bds03.entities.Department; import com.devsuperior.bds03.repositories.DepartmentRepository; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; @Service public class DepartmentService { - @Autowired - private DepartmentRepository repository; - - public List findAll() { + private final DepartmentRepository repository; + + public DepartmentService(DepartmentRepository repository) { + this.repository = repository; + } + + public List findAll() { List list = repository.findAll(Sort.by("name")); - return list.stream().map(x -> new DepartmentDTO(x)).collect(Collectors.toList()); + return list.stream().map(DepartmentDTO::new).collect(Collectors.toList()); } } diff --git a/src/main/java/com/devsuperior/bds03/services/EmployeeService.java b/src/main/java/com/devsuperior/bds03/services/EmployeeService.java index cb57083b..05f73634 100644 --- a/src/main/java/com/devsuperior/bds03/services/EmployeeService.java +++ b/src/main/java/com/devsuperior/bds03/services/EmployeeService.java @@ -1,26 +1,27 @@ package com.devsuperior.bds03.services; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.devsuperior.bds03.dto.EmployeeDTO; import com.devsuperior.bds03.entities.Department; import com.devsuperior.bds03.entities.Employee; import com.devsuperior.bds03.repositories.EmployeeRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class EmployeeService { - @Autowired private EmployeeRepository repository; - - @Transactional(readOnly = true) + + public EmployeeService(EmployeeRepository repository) { + this.repository = repository; + } + + @Transactional(readOnly = true) public Page findAll(Pageable pageable) { Page page = repository.findAll(pageable); - return page.map(x -> new EmployeeDTO(x)); + return page.map(EmployeeDTO::new); } @Transactional diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 08576c63..a1942d39 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,3 +1,13 @@ +INSERT INTO tb_user (email, password) VALUES ('alex@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); +INSERT INTO tb_user (email, password) VALUES ('maria@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); + +INSERT INTO tb_role (authority) VALUES ('ROLE_OPERATOR'); +INSERT INTO tb_role (authority) VALUES ('ROLE_ADMIN'); + +INSERT INTO tb_user_role (user_id, role_id) VALUES (1, 1); +INSERT INTO tb_user_role (user_id, role_id) VALUES (2, 1); +INSERT INTO tb_user_role (user_id, role_id) VALUES (2, 2); + INSERT INTO tb_department(name) VALUES ('Sales'); INSERT INTO tb_department(name) VALUES ('Management'); INSERT INTO tb_department(name) VALUES ('Training'); From 5375422e511fdfa0b7fac384255f02aed84e5551 Mon Sep 17 00:00:00 2001 From: Alisson Youssf Date: Sat, 6 Jan 2024 20:30:18 -0300 Subject: [PATCH 2/4] Validation infrastructure --- .../controllers/DepartmentController.java | 8 +-- .../bds03/controllers/EmployeeController.java | 19 ++---- .../controllers/exceptions/FieldMessage.java | 33 +++++++++++ .../exceptions/ResourceExceptionHandler.java | 58 +++++++++++++++++++ .../controllers/exceptions/StandardError.java | 57 ++++++++++++++++++ .../exceptions/ValidationError.java | 17 ++++++ .../exceptions/DatabaseException.java | 9 +++ .../exceptions/ResourceNotFoundException.java | 9 +++ 8 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/devsuperior/bds03/controllers/exceptions/FieldMessage.java create mode 100644 src/main/java/com/devsuperior/bds03/controllers/exceptions/ResourceExceptionHandler.java create mode 100644 src/main/java/com/devsuperior/bds03/controllers/exceptions/StandardError.java create mode 100644 src/main/java/com/devsuperior/bds03/controllers/exceptions/ValidationError.java create mode 100644 src/main/java/com/devsuperior/bds03/services/exceptions/DatabaseException.java create mode 100644 src/main/java/com/devsuperior/bds03/services/exceptions/ResourceNotFoundException.java diff --git a/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java b/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java index c0f34b2a..53dbc5b2 100644 --- a/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java +++ b/src/main/java/com/devsuperior/bds03/controllers/DepartmentController.java @@ -1,15 +1,13 @@ package com.devsuperior.bds03.controllers; -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; +import com.devsuperior.bds03.dto.DepartmentDTO; +import com.devsuperior.bds03.services.DepartmentService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.devsuperior.bds03.dto.DepartmentDTO; -import com.devsuperior.bds03.services.DepartmentService; +import java.util.List; @RestController @RequestMapping(value = "/departments") diff --git a/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java b/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java index 73cbb6c1..c133d489 100644 --- a/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java +++ b/src/main/java/com/devsuperior/bds03/controllers/EmployeeController.java @@ -1,24 +1,17 @@ package com.devsuperior.bds03.controllers; -import java.net.URI; - -import javax.validation.Valid; - -import org.springframework.beans.factory.annotation.Autowired; +import com.devsuperior.bds03.dto.EmployeeDTO; +import com.devsuperior.bds03.services.EmployeeService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import com.devsuperior.bds03.dto.EmployeeDTO; -import com.devsuperior.bds03.services.EmployeeService; +import javax.validation.Valid; +import java.net.URI; @RestController @RequestMapping(value = "/employees") @@ -38,7 +31,7 @@ public ResponseEntity> findAll(Pageable pageable) { } @PostMapping - public ResponseEntity insert(@RequestBody EmployeeDTO dto) { + public ResponseEntity insert(@Valid @RequestBody EmployeeDTO dto) { dto = service.insert(dto); URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}") .buildAndExpand(dto.getId()).toUri(); diff --git a/src/main/java/com/devsuperior/bds03/controllers/exceptions/FieldMessage.java b/src/main/java/com/devsuperior/bds03/controllers/exceptions/FieldMessage.java new file mode 100644 index 00000000..75573a63 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/controllers/exceptions/FieldMessage.java @@ -0,0 +1,33 @@ +package com.devsuperior.bds03.controllers.exceptions; + +import java.io.Serializable; + +public class FieldMessage implements Serializable { + private static final long serialVersionUID = 1L; + + private String fieldName; + private String message; + + public FieldMessage() {} + + public FieldMessage(String fieldName , String message) { + this.fieldName = fieldName; + this.message = message; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/devsuperior/bds03/controllers/exceptions/ResourceExceptionHandler.java b/src/main/java/com/devsuperior/bds03/controllers/exceptions/ResourceExceptionHandler.java new file mode 100644 index 00000000..c9744aff --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/controllers/exceptions/ResourceExceptionHandler.java @@ -0,0 +1,58 @@ +package com.devsuperior.bds03.controllers.exceptions; + +import com.devsuperior.bds03.services.exceptions.DatabaseException; +import com.devsuperior.bds03.services.exceptions.ResourceNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import javax.servlet.http.HttpServletRequest; +import java.time.Instant; + +@ControllerAdvice +public class ResourceExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity entityNotFound(ResourceNotFoundException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.NOT_FOUND; + StandardError err = new StandardError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Resource not found"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + return ResponseEntity.status(status).body(err); + } + + @ExceptionHandler(DatabaseException.class) + public ResponseEntity database(DatabaseException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.BAD_REQUEST; + StandardError err = new StandardError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Database exception"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + return ResponseEntity.status(status).body(err); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity validation(MethodArgumentNotValidException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY; + ValidationError err = new ValidationError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Validation exception"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + + for (FieldError f : e.getBindingResult().getFieldErrors()) { + err.addErrors(f.getField(), f.getDefaultMessage()); + } + + return ResponseEntity.status(status).body(err); + } +} diff --git a/src/main/java/com/devsuperior/bds03/controllers/exceptions/StandardError.java b/src/main/java/com/devsuperior/bds03/controllers/exceptions/StandardError.java new file mode 100644 index 00000000..5c55f2df --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/controllers/exceptions/StandardError.java @@ -0,0 +1,57 @@ +package com.devsuperior.bds03.controllers.exceptions; + +import java.io.Serializable; +import java.time.Instant; + +public class StandardError implements Serializable { + private static final long serialVersionUID = 1L; + + private Instant timestamp; + private Integer status; + private String error; + private String message; + private String path; + + public StandardError() { + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/src/main/java/com/devsuperior/bds03/controllers/exceptions/ValidationError.java b/src/main/java/com/devsuperior/bds03/controllers/exceptions/ValidationError.java new file mode 100644 index 00000000..c52114ab --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/controllers/exceptions/ValidationError.java @@ -0,0 +1,17 @@ +package com.devsuperior.bds03.controllers.exceptions; + +import java.util.ArrayList; +import java.util.List; + +public class ValidationError extends StandardError { + private static final long serialVersionUID = 1L; + private final List errors = new ArrayList<>(); + + public List getErrors() { + return errors; + } + + public void addErrors(String fieldName, String message) { + errors.add(new FieldMessage(fieldName, message)); + } +} diff --git a/src/main/java/com/devsuperior/bds03/services/exceptions/DatabaseException.java b/src/main/java/com/devsuperior/bds03/services/exceptions/DatabaseException.java new file mode 100644 index 00000000..e085a436 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/services/exceptions/DatabaseException.java @@ -0,0 +1,9 @@ +package com.devsuperior.bds03.services.exceptions; + +public class DatabaseException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public DatabaseException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/devsuperior/bds03/services/exceptions/ResourceNotFoundException.java b/src/main/java/com/devsuperior/bds03/services/exceptions/ResourceNotFoundException.java new file mode 100644 index 00000000..77f099d2 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/services/exceptions/ResourceNotFoundException.java @@ -0,0 +1,9 @@ +package com.devsuperior.bds03.services.exceptions; + +public class ResourceNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ResourceNotFoundException(String msg) { + super(msg); + } +} From 068408ddda18c0b9c800b8a38372fc78faad1e78 Mon Sep 17 00:00:00 2001 From: Alisson Youssf Date: Sat, 6 Jan 2024 23:08:08 -0300 Subject: [PATCH 3/4] Security infrastructure --- .../devsuperior/bds03/config/AppConfig.java | 30 ++++++++ .../config/AuthorizationServerConfig.java | 61 ++++++++++++++++ .../bds03/config/ResourceServerConfig.java | 49 +++++++++++++ .../bds03/config/SecurityConfig.java | 16 ----- .../bds03/config/WebSecurityConfig.java | 40 +++++++++++ .../com/devsuperior/bds03/dto/RoleDTO.java | 39 ++++++++++ .../com/devsuperior/bds03/dto/UserDTO.java | 72 +++++++++++++++++++ .../bds03/repositories/RoleRepository.java | 10 +++ .../bds03/repositories/UserRepository.java | 10 +++ .../bds03/services/UserService.java | 32 +++++++++ src/main/resources/application.properties | 9 ++- 11 files changed, 351 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/devsuperior/bds03/config/AppConfig.java create mode 100644 src/main/java/com/devsuperior/bds03/config/AuthorizationServerConfig.java create mode 100644 src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java delete mode 100644 src/main/java/com/devsuperior/bds03/config/SecurityConfig.java create mode 100644 src/main/java/com/devsuperior/bds03/config/WebSecurityConfig.java create mode 100644 src/main/java/com/devsuperior/bds03/dto/RoleDTO.java create mode 100644 src/main/java/com/devsuperior/bds03/dto/UserDTO.java create mode 100644 src/main/java/com/devsuperior/bds03/repositories/RoleRepository.java create mode 100644 src/main/java/com/devsuperior/bds03/repositories/UserRepository.java create mode 100644 src/main/java/com/devsuperior/bds03/services/UserService.java diff --git a/src/main/java/com/devsuperior/bds03/config/AppConfig.java b/src/main/java/com/devsuperior/bds03/config/AppConfig.java new file mode 100644 index 00000000..b04f2bf2 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/config/AppConfig.java @@ -0,0 +1,30 @@ +package com.devsuperior.bds03.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +@Configuration +public class AppConfig { + @Value("${jwt.secret}") + private String jwtSecret; + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public JwtAccessTokenConverter accessTokenConverter() { + JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter(); + tokenConverter.setSigningKey(jwtSecret); + return tokenConverter; + } + + @Bean + public JwtTokenStore tokenStore() { + return new JwtTokenStore(accessTokenConverter()); + } +} diff --git a/src/main/java/com/devsuperior/bds03/config/AuthorizationServerConfig.java b/src/main/java/com/devsuperior/bds03/config/AuthorizationServerConfig.java new file mode 100644 index 00000000..9a9c22ef --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/config/AuthorizationServerConfig.java @@ -0,0 +1,61 @@ +package com.devsuperior.bds03.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Value("${security.oauth2.client.client-id}") + private String clientId; + @Value("${security.oauth2.client.client-secret}") + private String clientSecret; + @Value("${jwt.duration}") + private Integer jwtDuration; + + private final BCryptPasswordEncoder passwordEncoder; + private final JwtAccessTokenConverter accessTokenConverter; + private final JwtTokenStore tokenStore; + private final AuthenticationManager authenticationManager; + + + public AuthorizationServerConfig(BCryptPasswordEncoder passwordEncoder , JwtAccessTokenConverter accessTokenConverter , JwtTokenStore tokenStore , AuthenticationManager authenticationManager) { + this.passwordEncoder = passwordEncoder; + this.accessTokenConverter = accessTokenConverter; + this.tokenStore = tokenStore; + this.authenticationManager = authenticationManager; + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); + } + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory() + .withClient(clientId) + .secret(passwordEncoder.encode(clientSecret)) + .scopes("read", "write") + .authorizedGrantTypes("password") + .accessTokenValiditySeconds(jwtDuration); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + + endpoints.authenticationManager(authenticationManager) + .tokenStore(tokenStore) + .accessTokenConverter(accessTokenConverter); + } +} diff --git a/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java b/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java new file mode 100644 index 00000000..dbdff76e --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java @@ -0,0 +1,49 @@ +package com.devsuperior.bds03.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +import java.util.Arrays; + +@Configuration +@EnableResourceServer +public class ResourceServerConfig extends ResourceServerConfigurerAdapter { + + private final JwtTokenStore tokenStore; + private final Environment env; + private static final String[] ADMIN = { "/users/**" }; + private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" }; + private static final String[] OPERATOR_OR_ADMIN = { "/departments/**", "/employees/**" }; + + public ResourceServerConfig(JwtTokenStore tokenStore , Environment env) { + this.tokenStore = tokenStore; + this.env = env; + } + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.tokenStore(tokenStore); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + + // H2 + if (Arrays.asList(env.getActiveProfiles()).contains("test")) { + http.headers().frameOptions().disable(); + } + + http.authorizeRequests() + .antMatchers(PUBLIC).permitAll() + .antMatchers(HttpMethod.GET, OPERATOR_OR_ADMIN).permitAll() + .antMatchers(OPERATOR_OR_ADMIN).hasAnyRole("OPERATOR", "ADMIN") + .antMatchers(ADMIN).hasRole("ADMIN") + .anyRequest().authenticated(); + } +} diff --git a/src/main/java/com/devsuperior/bds03/config/SecurityConfig.java b/src/main/java/com/devsuperior/bds03/config/SecurityConfig.java deleted file mode 100644 index 09a75cde..00000000 --- a/src/main/java/com/devsuperior/bds03/config/SecurityConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.devsuperior.bds03.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -@Configuration -@EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - @Override - public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers("/**"); - } -} diff --git a/src/main/java/com/devsuperior/bds03/config/WebSecurityConfig.java b/src/main/java/com/devsuperior/bds03/config/WebSecurityConfig.java new file mode 100644 index 00000000..f29105ff --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/config/WebSecurityConfig.java @@ -0,0 +1,40 @@ +package com.devsuperior.bds03.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final BCryptPasswordEncoder passwordEncoder; + private final UserDetailsService userDetailsService; + + public WebSecurityConfig(BCryptPasswordEncoder passwordEncoder , UserDetailsService userDetailsService) { + this.passwordEncoder = passwordEncoder; + this.userDetailsService = userDetailsService; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/actuator/**"); + } + + @Override + @Bean + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } +} diff --git a/src/main/java/com/devsuperior/bds03/dto/RoleDTO.java b/src/main/java/com/devsuperior/bds03/dto/RoleDTO.java new file mode 100644 index 00000000..84978bc2 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/dto/RoleDTO.java @@ -0,0 +1,39 @@ +package com.devsuperior.bds03.dto; + +import com.devsuperior.bds03.entities.Role; + +import java.io.Serializable; + +public class RoleDTO implements Serializable { + private static final long serialVersionUID = 1L; + private Long id; + private String authority; + + public RoleDTO() {} + + public RoleDTO(Long id , String authority) { + this.id = id; + this.authority = authority; + } + + public RoleDTO(Role role) { + id = role.getId(); + authority = role.getAuthority(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } +} diff --git a/src/main/java/com/devsuperior/bds03/dto/UserDTO.java b/src/main/java/com/devsuperior/bds03/dto/UserDTO.java new file mode 100644 index 00000000..8baff093 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/dto/UserDTO.java @@ -0,0 +1,72 @@ +package com.devsuperior.bds03.dto; + +import com.devsuperior.bds03.entities.User; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +public class UserDTO implements Serializable { + private static final long serialVersionUID = 1L; + private Long id; + @NotBlank(message = "Campo obrigatório") + private String firstName; + private String lastName; + + @Email(message = "Favor entrar com um e-mail válido") + private String email; + private Set roles = new HashSet<>(); + + public UserDTO() {} + + public UserDTO(Long id , String firstName , String lastName , String email) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } + + public UserDTO(User entity) { + id = entity.getId(); + email = entity.getEmail(); + entity.getRoles().forEach(role -> this.roles.add(new RoleDTO(role))); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Set getRoles() { + return roles; + } +} diff --git a/src/main/java/com/devsuperior/bds03/repositories/RoleRepository.java b/src/main/java/com/devsuperior/bds03/repositories/RoleRepository.java new file mode 100644 index 00000000..17406413 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/repositories/RoleRepository.java @@ -0,0 +1,10 @@ +package com.devsuperior.bds03.repositories; + +import com.devsuperior.bds03.entities.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RoleRepository extends JpaRepository { + +} diff --git a/src/main/java/com/devsuperior/bds03/repositories/UserRepository.java b/src/main/java/com/devsuperior/bds03/repositories/UserRepository.java new file mode 100644 index 00000000..374a1992 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/repositories/UserRepository.java @@ -0,0 +1,10 @@ +package com.devsuperior.bds03.repositories; + +import com.devsuperior.bds03.entities.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + User findByEmail(String email); +} diff --git a/src/main/java/com/devsuperior/bds03/services/UserService.java b/src/main/java/com/devsuperior/bds03/services/UserService.java new file mode 100644 index 00000000..7336ccc3 --- /dev/null +++ b/src/main/java/com/devsuperior/bds03/services/UserService.java @@ -0,0 +1,32 @@ +package com.devsuperior.bds03.services; + +import com.devsuperior.bds03.entities.User; +import com.devsuperior.bds03.repositories.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +@Service +public class UserService implements UserDetailsService { + + private static final Logger logger = LoggerFactory.getLogger(UserService.class); + private final UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User user = repository.findByEmail(username); + if (user == null) { + logger.error("User not found: " + username); + throw new UsernameNotFoundException("Email not found"); + } + logger.info("User found: " + username); + return user; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a7dcd8af..f6779e36 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,10 @@ -spring.profiles.active=test +spring.profiles.active=${APP_PROFILE:test} spring.jpa.open-in-view=false + +security.oauth2.client.client-id=${CLIENT_ID:myclientid} +security.oauth2.client.client-secret=${CLIENT_SECRET:myclientsecret} + +jwt.secret=${JWT_SECRET:MY-JWT-SECRET} +jwt.duration=${JWT_DURATION:86400} + From 82cc2b7944523ef9c9d11a0a451d3f06bfc42f85 Mon Sep 17 00:00:00 2001 From: Alisson Youssf Date: Sun, 7 Jan 2024 12:18:38 -0300 Subject: [PATCH 4/4] Final --- .../devsuperior/bds03/config/ResourceServerConfig.java | 9 +++------ src/main/java/com/devsuperior/bds03/dto/EmployeeDTO.java | 7 +++++++ src/main/resources/data.sql | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java b/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java index dbdff76e..bf815d5a 100644 --- a/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java +++ b/src/main/java/com/devsuperior/bds03/config/ResourceServerConfig.java @@ -17,9 +17,8 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private final JwtTokenStore tokenStore; private final Environment env; - private static final String[] ADMIN = { "/users/**" }; private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" }; - private static final String[] OPERATOR_OR_ADMIN = { "/departments/**", "/employees/**" }; + private static final String[] OPERATOR_GET = { "/departments/**", "/employees/**" }; public ResourceServerConfig(JwtTokenStore tokenStore , Environment env) { this.tokenStore = tokenStore; @@ -41,9 +40,7 @@ public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(PUBLIC).permitAll() - .antMatchers(HttpMethod.GET, OPERATOR_OR_ADMIN).permitAll() - .antMatchers(OPERATOR_OR_ADMIN).hasAnyRole("OPERATOR", "ADMIN") - .antMatchers(ADMIN).hasRole("ADMIN") - .anyRequest().authenticated(); + .antMatchers(HttpMethod.GET, OPERATOR_GET).hasAnyRole("OPERATOR", "ADMIN") + .anyRequest().hasAnyRole("ADMIN"); } } diff --git a/src/main/java/com/devsuperior/bds03/dto/EmployeeDTO.java b/src/main/java/com/devsuperior/bds03/dto/EmployeeDTO.java index 2402cfe3..db9dcdb8 100644 --- a/src/main/java/com/devsuperior/bds03/dto/EmployeeDTO.java +++ b/src/main/java/com/devsuperior/bds03/dto/EmployeeDTO.java @@ -4,12 +4,19 @@ import com.devsuperior.bds03.entities.Employee; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + public class EmployeeDTO implements Serializable { private static final long serialVersionUID = 1L; private Long id; + @NotBlank(message = "Campo requerido") private String name; + @Email(message = "Email inválido") private String email; + @NotNull(message = "Campo requerido") private Long departmentId; public EmployeeDTO() { diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index a1942d39..dcf151a0 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,5 +1,5 @@ -INSERT INTO tb_user (email, password) VALUES ('alex@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); -INSERT INTO tb_user (email, password) VALUES ('maria@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); +INSERT INTO tb_user (email, password) VALUES ('ana@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); +INSERT INTO tb_user (email, password) VALUES ('bob@gmail.com', '$2a$10$eACCYoNOHEqXve8aIWT8Nu3PkMXWBaOxJ9aORUYzfMQCbVBIhZ8tG'); INSERT INTO tb_role (authority) VALUES ('ROLE_OPERATOR'); INSERT INTO tb_role (authority) VALUES ('ROLE_ADMIN');