Spring Boot 3 - Buenas prácticas y protección: Aula 3
This commit is contained in:
parent
e9fbc012c2
commit
d2d56e2a91
@ -21,7 +21,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
@ -63,6 +62,15 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -0,0 +1,35 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.domain.usuario.DatosAutenticacionUsuario;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/login")
|
||||
public class AutenticacionController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity autenticarUsuario(@RequestBody @Valid DatosAutenticacionUsuario datosAutenticacionUsuario) {
|
||||
Authentication token = new UsernamePasswordAuthenticationToken(
|
||||
datosAutenticacionUsuario.login(),
|
||||
datosAutenticacionUsuario.clave());
|
||||
authenticationManager.authenticate(token);
|
||||
//var token = new UsernamePasswordAuthenticationToken(
|
||||
// datosAutenticacionUsuario.login(),
|
||||
// datosAutenticacionUsuario.clave());
|
||||
//var autentication = authenticationManager.authenticate(token);
|
||||
return ResponseEntity.ok().build();
|
||||
|
||||
}
|
||||
}
|
@ -29,21 +29,23 @@ public class MedicoController {
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
Medico medico = medicoRepository.save(new Medico(datosRegistroMedico));
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico.getId(), medico.getNombre(), medico.getEmail(), medico.getTelefono(), medico.getDocumento(),
|
||||
new DatosDireccion(
|
||||
medico.getDireccion().getCalle(), medico.getDireccion().getDistrito(), medico.getDireccion().getCiudad(),
|
||||
medico.getDireccion().getNumero(), medico.getDireccion().getComplemento()
|
||||
)
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
|
||||
//DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
// medico.getId(), medico.getNombre(), medico.getEmail(), medico.getTelefono(), medico.getDocumento(),
|
||||
// new DatosDireccion(
|
||||
// medico.getDireccion().getCalle(), medico.getDireccion().getDistrito(), medico.getDireccion().getCiudad(),
|
||||
// medico.getDireccion().getNumero(), medico.getDireccion().getComplemento()
|
||||
// )
|
||||
//);
|
||||
URI url = uriComponentsBuilder.path("/medicos/{id}") .buildAndExpand(medico.getId()).toUri();
|
||||
return ResponseEntity.created(url).body(datosRespuestaMedico);
|
||||
// Debe retornar 201 Created
|
||||
// Url donde encontrar al médico ej. http://127.0.0.1:8080/medicos/xx
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<DatosListadoMedicos>> listadoMedicos(@PageableDefault(size = 5) Pageable paginacion) {
|
||||
//return medicoRepository.findAll(paginacion).map(DatosListadoMedicos::new);
|
||||
public ResponseEntity<Page<DatosListadoMedicos>> listadoMedicos(
|
||||
@PageableDefault(size = 5) Pageable paginacion) {
|
||||
return ResponseEntity.ok(medicoRepository.findByActivoTrue(paginacion).map(DatosListadoMedicos::new));
|
||||
}
|
||||
|
||||
@ -53,16 +55,12 @@ public class MedicoController {
|
||||
@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
|
||||
Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id());
|
||||
medico.actualizarDatos(datosActualizarMedico);
|
||||
return ResponseEntity.ok(new DatosRespuestaMedico(
|
||||
medico.getId(), medico.getNombre(), medico.getEmail(), medico.getTelefono(), medico.getDocumento(),
|
||||
new DatosDireccion(
|
||||
medico.getDireccion().getCalle(), medico.getDireccion().getDistrito(), medico.getDireccion().getCiudad(),
|
||||
medico.getDireccion().getNumero(), medico.getDireccion().getComplemento()
|
||||
)
|
||||
));
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaMedico);
|
||||
}
|
||||
|
||||
// Desactivar Medico
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ResponseEntity eliminarMedico(@PathVariable Long id) {
|
||||
@ -74,14 +72,10 @@ public class MedicoController {
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<DatosRespuestaMedico> retornaDatosMedico(@PathVariable Long id) {
|
||||
Medico medico = medicoRepository.getReferenceById(id);
|
||||
var datosMedico = new DatosRespuestaMedico(
|
||||
medico.getId(), medico.getNombre(), medico.getEmail(), medico.getTelefono(), medico.getDocumento(),
|
||||
new DatosDireccion(
|
||||
medico.getDireccion().getCalle(), medico.getDireccion().getDistrito(), medico.getDireccion().getCiudad(),
|
||||
medico.getDireccion().getNumero(), medico.getDireccion().getComplemento()
|
||||
)
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosMedico);
|
||||
return ResponseEntity.ok(datosRespuestaMedico);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,35 +29,30 @@ public class PacienteController {
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
Paciente paciente = pacienteRepository.save(new Paciente(datosRegistroPaciente));
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente.getId(), paciente.getNombre(), paciente.getEmail(), paciente.getTelefono(), paciente.getDocumento(),
|
||||
new DatosDireccion(
|
||||
paciente.getDireccion().getCalle(), paciente.getDireccion().getDistrito(), paciente.getDireccion().getCiudad(),
|
||||
paciente.getDireccion().getNumero(), paciente.getDireccion().getComplemento()
|
||||
)
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
URI url = uriComponentsBuilder.path("/pacientes/{id}") .buildAndExpand(paciente.getId()).toUri();
|
||||
return ResponseEntity.created(url).body(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<DatosListadoPacientes>> listadoPacientes(@PageableDefault(size = 5) Pageable paginacion) {
|
||||
//return pacienteRepository.findAll(paginacion).map(DatosListadoPacientes::new);
|
||||
return ResponseEntity.ok(pacienteRepository.findByActivoTrue(paginacion).map(DatosListadoPacientes::new));
|
||||
public ResponseEntity<Page<DatosListadoPacientes>> listadoPacientes(
|
||||
@PageableDefault(size = 5) Pageable paginacion) {
|
||||
return ResponseEntity.ok(
|
||||
pacienteRepository.findByActivoTrue(paginacion).map(DatosListadoPacientes::new)
|
||||
);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Transactional
|
||||
public ResponseEntity<DatosRespuestaPaciente> actualizarPaciente(@RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) {
|
||||
public ResponseEntity<DatosRespuestaPaciente> actualizarPaciente(
|
||||
@RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) {
|
||||
Paciente paciente = pacienteRepository.getReferenceById(datosActualizarPaciente.id());
|
||||
paciente.actualizarDatos(datosActualizarPaciente);
|
||||
return ResponseEntity.ok(new DatosRespuestaPaciente(
|
||||
paciente.getId(), paciente.getNombre(), paciente.getEmail(), paciente.getTelefono(), paciente.getDocumento(),
|
||||
new DatosDireccion(
|
||||
paciente.getDireccion().getCalle(), paciente.getDireccion().getDistrito(),
|
||||
paciente.getDireccion().getCiudad(),paciente.getDireccion().getNumero(),
|
||||
paciente.getDireccion().getComplemento()
|
||||
)
|
||||
));
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
// Desactivar Paciente
|
||||
@ -72,14 +67,10 @@ public class PacienteController {
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<DatosRespuestaPaciente> retornaDatosPaciente(@PathVariable Long id) {
|
||||
Paciente paciente = pacienteRepository.getReferenceById(id);
|
||||
var datosPaciente = new DatosRespuestaPaciente(
|
||||
paciente.getId(), paciente.getNombre(), paciente.getEmail(), paciente.getTelefono(), paciente.getDocumento(),
|
||||
new DatosDireccion(
|
||||
paciente.getDireccion().getCalle(), paciente.getDireccion().getDistrito(), paciente.getDireccion().getCiudad(),
|
||||
paciente.getDireccion().getNumero(), paciente.getDireccion().getComplemento()
|
||||
)
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosPaciente);
|
||||
return ResponseEntity.ok(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,4 +8,13 @@ public record DatosDireccion(
|
||||
@NotBlank String ciudad,
|
||||
String numero,
|
||||
String complemento) {
|
||||
|
||||
public DatosDireccion(Direccion direccion){
|
||||
this(direccion.getCalle(),
|
||||
direccion.getDistrito(),
|
||||
direccion.getCiudad(),
|
||||
direccion.getNumero(),
|
||||
direccion.getComplemento()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,14 @@ import med.voll.api.domain.direccion.DatosDireccion;
|
||||
public record DatosRespuestaMedico(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
||||
DatosDireccion direccion) {
|
||||
|
||||
public DatosRespuestaMedico(Medico medico, DatosDireccion direccion){
|
||||
this(medico.getId(),
|
||||
medico.getNombre(),
|
||||
medico.getEmail(),
|
||||
medico.getTelefono(),
|
||||
medico.getDocumento(),
|
||||
direccion);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,4 +6,14 @@ import med.voll.api.domain.direccion.DatosDireccion;
|
||||
public record DatosRespuestaPaciente(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
||||
DatosDireccion direccion) {
|
||||
|
||||
public DatosRespuestaPaciente(Paciente paciente, DatosDireccion direccion){
|
||||
this(paciente.getId(),
|
||||
paciente.getNombre(),
|
||||
paciente.getEmail(),
|
||||
paciente.getTelefono(),
|
||||
paciente.getDocumento(),
|
||||
direccion);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
public record DatosAutenticacionUsuario(String login, String clave) {
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "usuarios")
|
||||
@Entity(name = "Usuario")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Usuario implements UserDetails {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String login;
|
||||
private String clave;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return clave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return login;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
|
||||
UserDetails findByLogin(String login);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package med.voll.api.infra;
|
||||
package med.voll.api.infra.errores;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
@ -21,6 +22,12 @@ public class ManejadorDeErrores {
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity manejarError500(DataIntegrityViolationException e) {
|
||||
var errores = e.getMostSpecificCause().getLocalizedMessage();
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
private record DatosErrorValidacion(String campo, String error) {
|
||||
public DatosErrorValidacion(FieldError error) {
|
||||
this(error.getField(), error.getDefaultMessage());
|
@ -0,0 +1,20 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
import med.voll.api.domain.usuario.UsuarioRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 AutenticacionService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private UsuarioRepository usuarioRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
|
||||
return usuarioRepository.findByLogin(login);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
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.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfigurations {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
|
||||
return httpSecurity.csrf().disable().sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder () {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
create table usuarios(
|
||||
|
||||
id bigint not null auto_increment,
|
||||
login varchar(100) not null unique,
|
||||
clave varchar(300) not null,
|
||||
|
||||
primary key(id)
|
||||
|
||||
);
|
@ -304,3 +304,263 @@ public record DatosRegistroMedico(
|
||||
@Valid DatosDireccion direccion) {}
|
||||
```
|
||||
|
||||
### Seguridad
|
||||
|
||||
- Autenticación
|
||||
- Autorización
|
||||
- Protección contra ataques (CSRF, clickjacking)
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'cardinal'}}}%%
|
||||
flowchart
|
||||
direction TB
|
||||
DB[(BBDD)]
|
||||
subgraph "Autenticación"
|
||||
subgraph APP["App o Web"]
|
||||
direction LR
|
||||
WEB("User
|
||||
Pass")
|
||||
end
|
||||
subgraph REQ["HTTP Request"]
|
||||
direction TB
|
||||
DT{"user
|
||||
pass"}
|
||||
JWT{JWT}
|
||||
end
|
||||
subgraph API[API REST]
|
||||
direction LR
|
||||
Aplicación
|
||||
end
|
||||
APP --"user & pass"--> DT
|
||||
JWT --"token"--> APP
|
||||
REQ <--> API
|
||||
API <--SQL--> DB
|
||||
end
|
||||
```
|
||||
|
||||
### Hash de contraseña
|
||||
|
||||
Al implementar una funcionalidad de autenticación en una aplicación,
|
||||
independientemente del lenguaje de programación utilizado, deberá tratar con
|
||||
los datos de inicio de sesión y contraseña de los usuarios, y deberán
|
||||
almacenarse en algún lugar, como, por ejemplo, una base de datos.
|
||||
|
||||
Las contraseñas son información confidencial y no deben almacenarse en texto
|
||||
sin formato, ya que si una persona malintencionada logra acceder a la base de
|
||||
datos, podrá acceder a las contraseñas de todos los usuarios. Para evitar este
|
||||
problema, siempre debe usar algún algoritmo hash en las contraseñas antes de
|
||||
almacenarlas en la base de datos.
|
||||
|
||||
**Hashing** no es más que una función matemática que convierte un texto en otro
|
||||
texto totalmente diferente y difícil de deducir. Por ejemplo, el texto *"Mi
|
||||
nombre es Rodrigo"* se puede convertir en el texto
|
||||
`8132f7cb860e9ce4c1d9062d2a5d1848`, utilizando el algoritmo ***hash MD5***.
|
||||
|
||||
Un detalle importante es que los algoritmos de hash deben ser unidireccionales,
|
||||
es decir, no debe ser posible obtener el texto original a partir de un hash.
|
||||
Así, para saber si un usuario ingresó la contraseña correcta al intentar
|
||||
autenticarse en una aplicación, debemos tomar la contraseña que ingresó y
|
||||
generar su hash, para luego compararla con el hash que está almacenado en la
|
||||
base de datos.
|
||||
|
||||
Hay varios algoritmos hashing que se pueden usar para transformar las contraseñas
|
||||
de los usuarios, algunos de los cuales son más antiguos y ya **no se consideran
|
||||
seguros** en la actualidad, como **MD5** y **SHA1**. Los principales algoritmos
|
||||
actualmente recomendados son:
|
||||
|
||||
- Bcrypt
|
||||
- Scrypt
|
||||
- Argon2
|
||||
- PBKDF2
|
||||
|
||||
Se utilizará el algoritmo **BCrypt**, que es bastante popular hoy en día. Esta
|
||||
opción también tiene en cuenta que ***Spring Security*** ya nos proporciona una
|
||||
clase que lo implementa.
|
||||
|
||||
**Spring Data** usa su propio patrón de nomenclatura de métodos a seguir para
|
||||
que pueda generar consultas SQL correctamente.
|
||||
|
||||
Hay algunas palabras reservadas que debemos usar en los nombres de los métodos,
|
||||
como `findBy` y `existBy`, para indicarle a **Spring Data** cómo debe ensamblar
|
||||
la consulta que queremos. Esta característica es bastante flexible y puede ser
|
||||
un poco compleja debido a las diversas posibilidades existentes.
|
||||
|
||||
Para conocer más detalles y comprender mejor cómo ensamblar consultas dinámicas
|
||||
con Spring Data, acceda a su
|
||||
[documentación oficial](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/).
|
||||
|
||||
Bcrypt [online](https://www.browserling.com/tools/bcrypt)
|
||||
|
||||
#### Autenticación API
|
||||
|
||||
Se agregan las dependencias
|
||||
|
||||
[pom.xml](./api_rest/api2/pom.xml)
|
||||
|
||||
```xml
|
||||
...
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
...
|
||||
```
|
||||
|
||||
Creación de clases `Usuario`, `UsuarioRepository` y `DatosAutenticacionUsuario`
|
||||
en *package*
|
||||
[domain.usuario](./api_rest/api2/src/main/java/med/voll/api/domain/usuario/)
|
||||
|
||||
[Usuario](./api_rest/api2/src/main/java/med/voll/api/domain/usuario/Usuario.java)
|
||||
|
||||
```java
|
||||
@Table(name = "usuarios")
|
||||
@Entity(name = "Usuario")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Usuario implements UserDetails {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String login;
|
||||
private String clave;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() { return clave; }
|
||||
|
||||
@Override
|
||||
public String getUsername() { return login; }
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() { return true; }
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() { return true; }
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() { return true; }
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() { return true; }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
[UsuarioRepository](./api_rest/api2/src/main/java/med/voll/api/domain/usuario/UsuarioRepository.java)
|
||||
|
||||
```java
|
||||
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
|
||||
UserDetails findByLogin(String login);
|
||||
}
|
||||
```
|
||||
|
||||
[DatosAutenticacionUsuario](./api_rest/api2/src/main/java/med/voll/api/domain/usuario/DatosAutenticacionUsuario.java)
|
||||
|
||||
```java
|
||||
public record DatosAutenticacionUsuario(String login, String clave) {}
|
||||
```
|
||||
|
||||
Creación de clase
|
||||
[AutenticacionController](./api_rest/api2/src/main/java/med/voll/api/controller/AutenticacionController.java)
|
||||
en *package* [controller](./api_rest/api2/src/main/java/med/voll/api/controller/)
|
||||
|
||||
> En este punto no retorna *token*
|
||||
|
||||
```java
|
||||
package med.voll.api.controller;
|
||||
@RestController
|
||||
@RequestMapping("/login")
|
||||
public class AutenticacionController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity autenticarUsuario(
|
||||
@RequestBody @Valid DatosAutenticacionUsuario datosAutenticacionUsuario) {
|
||||
Authentication token = new UsernamePasswordAuthenticationToken(
|
||||
datosAutenticacionUsuario.login(),
|
||||
datosAutenticacionUsuario.clave());
|
||||
authenticationManager.authenticate(token);
|
||||
return ResponseEntity.ok().build();
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Creación de clases `AutenticationService` y `SecurityConfigurations` en *package*
|
||||
[infra.security](./api_rest/api2/src/main/java/med/voll/api/infra/)
|
||||
|
||||
[AutenticationService](./api_rest/api2/src/main/java/med/voll/api/infra/security/AutenticacionService.java)
|
||||
|
||||
```java
|
||||
package med.voll.api.infra.security;
|
||||
@Service
|
||||
public class AutenticacionService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private UsuarioRepository usuarioRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String login)
|
||||
throws UsernameNotFoundException {
|
||||
return usuarioRepository.findByLogin(login);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[SecurityConfigurations](./api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java)
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfigurations {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity)
|
||||
throws Exception {
|
||||
return httpSecurity.csrf().disable().sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
AuthenticationConfiguration authenticationConfiguration)
|
||||
throws Exception {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder () {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Creación de nueva
|
||||
[migración](./api_rest/api2/src/main/resources/db/migration/V5__create-table-usuarios.sql)
|
||||
para crear tabla `usuarios`
|
||||
|
||||
```sql
|
||||
create table usuarios(
|
||||
id bigint not null auto_increment,
|
||||
login varchar(100) not null unique,
|
||||
clave varchar(300) not null,
|
||||
primary key(id)
|
||||
);
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user