Commit e8feaa98 by Antonio Rueda

Adaptación a Spring Security 6

parents 9ca0772c f67d63c9
## Test de carga del API REST con LOCUST
Es necesario tener python 3 y la herramienta LOCUST. Puede instalarse mediante
`pip3 install locust`.
Revisar fichero locust.py con los tests a realizar. Ejecutar `locust`
en esta carpeta desde la línea de comandos y lanzar la herramienta
web de testing en http://localhost:8089.
No preview for this file type
import time
from locust import FastHttpUser, task
from random import randint
import requests
"""
class TestCargaUjaCoin(FastHttpUser):
usuario = "23389229D"
cuenta = "2546556012"
auth_info = (usuario, "secret")
@task(4)
def leer_info_usuario(self):
self.client.get(f"/ujacoin/clientes/{self.usuario}", auth = self.auth_info)
@task(4)
def leer_info_cuenta(self):
self.client.get(f"/ujacoin/clientes/{self.usuario}/cuentas/{self.cuenta}", auth = self.auth_info)
@task(1)
def ingresar_1_euro(self):
self.client.post(f"/ujacoin/clientes/{self.usuario}/cuentas/{self.cuenta}/movimientos",
json={"tipo": "ingreso", "numTarjeta": "4111111111111111", "numCuenta": "5729089977", "importe": "1.0"}, auth = self.auth_info)
"""
class TestCargaUjaCoin(FastHttpUser):
def on_start(self):
usuario_creado = False
while not usuario_creado:
random_dni = randint(1000000, 9999999)
self.dni_usuario = f"3{random_dni}D"
self.clave_usuario = "secret"
self.auth = (self.dni_usuario, self.clave_usuario)
# Crear usuario con dni y nombre aleatorio
response = self.client.post("/ujacoin/clientes", json={
"dni": self.dni_usuario,
"nombre": f"Usuario{random_dni} España España",
"fNacimiento": "1980-01-01",
"direccion": "Av Madrid, 1 - Jaén",
"tlf": "611111111",
"email": "user@gmail.com",
"clave": self.clave_usuario
})
# En caso de éxito guardar cuenta y crear tarjeta
if response.status_code == requests.codes.created:
self.cuenta_usuario = response.json().get("num")
random_tarjeta = randint(10000000000000, 99999999999999)
self.tarjeta_usuario = f"55{random_tarjeta}"
self.client.post(f"/ujacoin/clientes/{self.dni_usuario}/tarjetas", json={
"num": self.tarjeta_usuario,
"titular": f"Usuario{random_dni} España España",
"fechaCaducidad": "2024-01-01",
"cvc": "123"
}, auth = self.auth)
usuario_creado = True
@task(5)
def leer_info_usuario(self):
self.client.get(f"/ujacoin/clientes/{self.dni_usuario}", auth = self.auth)
@task(1)
def ingresar_1_euro(self):
self.client.post(f"/ujacoin/clientes/{self.dni_usuario}/cuentas/{self.cuenta_usuario}/movimientos",
json={"tipo": "ingreso", "numTarjeta": self.tarjeta_usuario, "numCuenta": self.cuenta_usuario, "importe": "1.0"}, auth = self.auth)
@task(5)
def leer_info_cuenta(self):
self.client.get(f"/ujacoin/clientes/{self.dni_usuario}/cuentas/{self.cuenta_usuario}", auth = self.auth)
......@@ -19,7 +19,7 @@
<properties>
<exec.vmArgs></exec.vmArgs>
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
<exec.appArgs></exec.appArgs>
<exec.appArgs>-server</exec.appArgs>
<exec.mainClass>es.ujaen.dae.ujacoin.app.UjaCoinApp</exec.mainClass>
<exec.executable>java</exec.executable>
</properties>
......@@ -36,7 +36,7 @@
<properties>
<exec.vmArgs>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}</exec.vmArgs>
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
<exec.appArgs></exec.appArgs>
<exec.appArgs>-server</exec.appArgs>
<exec.mainClass>es.ujaen.dae.ujacoin.app.UjaCoinApp</exec.mainClass>
<exec.executable>java</exec.executable>
<jpda.listen>true</jpda.listen>
......@@ -56,7 +56,7 @@
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
<exec.mainClass>es.ujaen.dae.ujacoin.app.UjaCoinApp</exec.mainClass>
<exec.executable>java</exec.executable>
<exec.appArgs></exec.appArgs>
<exec.appArgs>-server</exec.appArgs>
</properties>
</action>
</actions>
......@@ -28,17 +28,25 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
......@@ -57,6 +65,30 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<!-- <scope>test</scope> -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
</dependencies>
<build>
......
......@@ -3,17 +3,22 @@ package es.ujaen.dae.ujacoin.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
/**
* Clase principal
*
* @author ajrueda
*/
@SpringBootApplication(scanBasePackages={
"es.ujaen.dae.ujacoin.servicios",
"es.ujaen.dae.ujacoin.repositorios"
@SpringBootApplication(scanBasePackages = {
"es.ujaen.dae.ujacoin.servicios",
"es.ujaen.dae.ujacoin.repositorios",
"es.ujaen.dae.ujacoin.rest",
"es.ujaen.dae.ujacoin.seguridad"
})
@EntityScan(basePackages="es.ujaen.dae.ujacoin.entidades")
public class UjaCoinApp {
@EntityScan(basePackages = "es.ujaen.dae.ujacoin.entidades")
@EnableCaching
public class UjaCoinApp {
public static void main(String[] args) throws Exception {
// Creación de servidor
SpringApplication.run(UjaCoinApp.class, args);
......
package es.ujaen.dae.ujacoin.entidades;
import es.ujaen.dae.ujacoin.util.ExprReg;
import es.ujaen.dae.ujacoin.util.CodificadorMd5;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
......@@ -14,6 +13,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import es.ujaen.dae.ujacoin.util.CodificadorPassword;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
......@@ -44,6 +44,7 @@ public class Cliente implements Serializable {
/** Dirección del domicilio */
@NotBlank
String direccion;
/** Teléfono */
@NotNull
@Size(min=9, max=13)
......@@ -80,7 +81,8 @@ public class Cliente implements Serializable {
this.tlf = tlf;
this.email = email;
this.clave = (clave != null ? CodificadorMd5.codificar(clave) : null);
this.clave = (clave != null ? CodificadorPassword.codificar(clave) : null);
// this.clave = clave;
tarjetas = new ArrayList<>();
cuentas = new ArrayList<>();
......@@ -111,12 +113,17 @@ public class Cliente implements Serializable {
}
/**
* Compara la clave con la del cliente, codificándola en Md5
* Compara la clave con la del cliente, codificándola con el algoritmo establecido
* @param clave
* @return
*/
public boolean claveValida(String clave) {
return this.clave.equals(CodificadorMd5.codificar(clave));
return CodificadorPassword.igual(clave, this.clave);
//return this.clave.equals(clave);
}
public String getClave() {
return clave;
}
/**
......
......@@ -10,6 +10,8 @@ import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.Optional;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
......@@ -24,15 +26,18 @@ public class RepositorioClientes {
@PersistenceContext
EntityManager em;
@Cacheable("clientes")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Optional<Cliente> buscar(String dni) {
return Optional.ofNullable(em.find(Cliente.class, dni));
}
@CacheEvict(value="clientes", key = "#cliente.dni")
public void guardar(Cliente cliente) {
em.persist(cliente);
}
@CacheEvict(value="clientes", key = "#cliente.dni")
public void nuevaTarjeta(Cliente cliente, Tarjeta tarjeta) {
em.persist(tarjeta);
......@@ -40,6 +45,7 @@ public class RepositorioClientes {
cliente.nuevaTarjeta(tarjeta);
}
@CacheEvict(value="clientes", key = "#cliente.dni")
public void actualizar(Cliente cliente) {
em.merge(cliente);
}
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.rest;
import es.ujaen.dae.ujacoin.rest.dto.DTOCliente;
import es.ujaen.dae.ujacoin.rest.dto.DTOCuenta;
import es.ujaen.dae.ujacoin.rest.dto.DTOMovimiento;
import es.ujaen.dae.ujacoin.rest.dto.DTOTarjeta;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
import es.ujaen.dae.ujacoin.entidades.movimientos.Movimiento;
import es.ujaen.dae.ujacoin.excepciones.ClienteNoRegistrado;
import es.ujaen.dae.ujacoin.excepciones.ClienteYaRegistrado;
import es.ujaen.dae.ujacoin.excepciones.SaldoInsuficienteParaOperacion;
import es.ujaen.dae.ujacoin.excepciones.TarjetaNoRegistrada;
import es.ujaen.dae.ujacoin.excepciones.TarjetaYaRegistrada;
import es.ujaen.dae.ujacoin.servicios.ServicioUjaCoin;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.constraints.Positive;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/**
* Controlador REST para los servicios de UjaCoin
*
* @author ajrueda
*/
@RestController
@RequestMapping("/ujacoin")
public class ControladorREST {
@Autowired
ServicioUjaCoin servicios;
/**
* Handler para excepciones de violación de restricciones
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public void handlerViolacionRestricciones(ConstraintViolationException e) {
// return ResponseEntity.badRequest().body(e.getMessage());
}
/**
* Handler para excepciones de accesos de usuarios no registrados
*/
@ExceptionHandler(ClienteNoRegistrado.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handlerClienteNoRegistrado(ClienteNoRegistrado e) {
}
/**
* Creación de clientes
*/
@PostMapping("/clientes")
ResponseEntity<DTOCuenta> altaCliente(@RequestBody DTOCliente cliente) {
try {
Cuenta cuenta = servicios.altaCliente(cliente.aCliente());
return ResponseEntity.status(HttpStatus.CREATED).body(new DTOCuenta(cuenta));
} catch (ClienteYaRegistrado e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
/**
* Login de clientes (temporal hasta incluir autenticación mediante Spring
* Security
*/
@GetMapping("/clientes/{dni}")
ResponseEntity<DTOCliente> verCliente(@PathVariable String dni) {
Optional<Cliente> cliente = servicios.verCliente(dni);
return cliente
.map(c -> ResponseEntity.ok(new DTOCliente(c)))
.orElse(ResponseEntity.notFound().build());
}
/**
* Registrar tarjetas
*/
@PostMapping("/clientes/{dni}/tarjetas")
ResponseEntity<Void> altaTarjeta(@PathVariable String dni, @RequestBody DTOTarjeta tarjeta) {
try {
servicios.registrarTarjeta(dni, tarjeta.aTarjeta());
return ResponseEntity.status(HttpStatus.CREATED).build();
} catch (TarjetaYaRegistrada e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
/**
* Listar tarjetas
*/
@GetMapping("/clientes/{dni}/tarjetas")
@ResponseStatus(HttpStatus.OK)
List<DTOTarjeta> verTarjetas(@PathVariable String dni) {
return servicios.verTarjetas(dni).stream()
.map(t -> new DTOTarjeta(t)).toList();
}
/**
* Obtener una tarjeta concreta
*/
@GetMapping("/clientes/{dni}/tarjetas/{num}")
ResponseEntity<DTOTarjeta> verTarjeta(@PathVariable String dni, @PathVariable String num) {
return servicios.verTarjetas(dni).stream()
.filter(t -> t.getNum().equals(num))
.findFirst()
.map(t -> ResponseEntity.ok(new DTOTarjeta(t)))
.orElse(ResponseEntity.notFound().build());
}
/**
* Crear cuentas adicionales
*/
@PostMapping("/clientes/{dni}/cuentas")
@ResponseStatus(HttpStatus.CREATED)
DTOCuenta altaCuenta(@PathVariable String dni) {
return new DTOCuenta(servicios.crearCuenta(dni));
}
/**
* Listar cuentas del cliente
*/
@GetMapping("/clientes/{dni}/cuentas")
@ResponseStatus(HttpStatus.OK)
List<DTOCuenta> verCuentas(@PathVariable String dni) {
return servicios.verCuentas(dni).stream()
.map(c -> new DTOCuenta(c)).toList();
}
/**
* Obtener una cuenta concreta
*/
@GetMapping("/clientes/{dni}/cuentas/{num}")
ResponseEntity<DTOCuenta> verCuenta(@PathVariable String dni, @PathVariable String num) {
return servicios.verCuentas(dni).stream()
.filter(c -> c.getNum().equals(num))
.findFirst()
.map(c -> ResponseEntity.ok(new DTOCuenta(c)))
.orElse(ResponseEntity.notFound().build());
}
/**
* Registrar un nuevo movimiento en la cuenta
*/
@PostMapping("/clientes/{dni}/cuentas/{numCuenta}/movimientos")
ResponseEntity<Void> registrarMovimiento(@PathVariable String dni, @PathVariable String numCuenta, @RequestBody DTOMovimiento movimiento) {
if (movimiento.tipo() == null) {
return ResponseEntity.badRequest().build();
}
try {
switch (movimiento.tipo()) {
case DTOMovimiento.INGRESO:
servicios.ingreso(numCuenta, movimiento.numTarjeta(), movimiento.importe());
break;
case DTOMovimiento.REINTEGRO:
servicios.reintegro(numCuenta, movimiento.numTarjeta(), movimiento.importe());
break;
case DTOMovimiento.TRANSFERENCIA_EMITIDA:
servicios.transferencia(numCuenta, movimiento.numCuenta(), movimiento.importe());
break;
default:
return ResponseEntity.badRequest().build();
}
} catch (SaldoInsuficienteParaOperacion e) {
return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).build();
} catch (TarjetaNoRegistrada t) {
return ResponseEntity.status(HttpStatus.FAILED_DEPENDENCY).build();
}
return ResponseEntity.status(HttpStatus.CREATED).build();
}
/**
* Listar movimientos, acotados por fechas y paginados
*/
@GetMapping("/clientes/{dni}/cuentas/{numCuenta}/movimientos")
ResponseEntity<List<DTOMovimiento>> verMovimientos(
@PathVariable String dni,
@PathVariable String numCuenta,
@RequestParam(required = false) String desdeFecha,
@RequestParam(required = false) String hastaFecha,
@RequestParam(required = false, defaultValue = "1") @Positive int pag,
@RequestParam(required = false, defaultValue = "10") @Positive int num) {
LocalDateTime fechaInicial;
LocalDateTime fechaFinal;
try {
fechaInicial = desdeFecha != null ? LocalDateTime.parse(desdeFecha) : null;
fechaFinal = hastaFecha != null ? LocalDateTime.parse(hastaFecha) : null;
} catch (DateTimeParseException e) {
return ResponseEntity.badRequest().build();
}
List<Movimiento> movimientos = servicios.listarMovimientosCuentaDesdeHasta(numCuenta, fechaInicial, fechaFinal);
return ResponseEntity.ok(movimientos.stream()
.skip((pag - 1) * num)
.limit(num)
.map(m -> DTOMovimiento.dtoMovimiento(m))
.toList());
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.rest.dto;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import java.time.LocalDate;
/**
* DTO para recopilación de datos de cliente
* @author ajrueda
*/
public record DTOCliente(
String dni,
String nombre,
LocalDate fNacimiento,
String direccion,
String tlf,
String email,
String clave) {
public DTOCliente(Cliente cliente) {
this(cliente.getDni(),
cliente.getNombre(),
cliente.getfNacimiento(),
cliente.getDireccion(),
cliente.getTlf(),
cliente.getEmail(),
"");
}
public Cliente aCliente() {
return new Cliente(dni, nombre, fNacimiento, direccion, tlf, email, clave);
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.rest.dto;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
/**
* DTO para información de cuentas bancarias en UJACoin
* @author ajrueda
*/
public record DTOCuenta(
String num,
float saldo,
String titular) {
public DTOCuenta(Cuenta cuenta) {
this(cuenta.getNum(), cuenta.getSaldo(), cuenta.getTitular().getDni());
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.rest.dto;
import es.ujaen.dae.ujacoin.entidades.movimientos.Ingreso;
import es.ujaen.dae.ujacoin.entidades.movimientos.Movimiento;
import es.ujaen.dae.ujacoin.entidades.movimientos.Reintegro;
import es.ujaen.dae.ujacoin.entidades.movimientos.TransferenciaEmitida;
import es.ujaen.dae.ujacoin.entidades.movimientos.TransferenciaRecibida;
/**
* DTO para codificación de movimientos
* @author ajrueda
*/
public record DTOMovimiento(
String tipo,
String numTarjeta,
String numCuenta,
float importe) {
public static final String INGRESO = "ingreso";
public static final String REINTEGRO = "reintegro";
public static final String TRANSFERENCIA_EMITIDA = "transferenciaEmitida";
public static final String TRANSFERENCIA_RECIBIDA = "transferenciaRecibida";
public static DTOMovimiento dtoIngreso(String numTarjeta, float importe) {
return new DTOMovimiento(INGRESO, numTarjeta, null, importe);
}
public static DTOMovimiento dtoReintegro(String numTarjeta, float importe) {
return new DTOMovimiento(REINTEGRO, numTarjeta, null, importe);
}
public static DTOMovimiento dtoTransferenciaEmitida(String numCuentaDestino, float importe) {
return new DTOMovimiento(TRANSFERENCIA_EMITIDA, null, numCuentaDestino, importe);
}
public static DTOMovimiento dtoTransferenciaRecibida(String numCuentaOrigen, float importe) {
return new DTOMovimiento(TRANSFERENCIA_RECIBIDA, null, numCuentaOrigen, importe);
}
public static DTOMovimiento dtoMovimiento(Movimiento movimiento) {
DTOMovimiento dto = null;
if (movimiento instanceof Ingreso ingreso) {
dto = dtoIngreso(ingreso.getTarjeta().getNum(), movimiento.getImporte());
}
else if (movimiento instanceof Reintegro reintegro) {
dto = dtoReintegro(reintegro.getTarjeta().getNum(), movimiento.getImporte());
}
else if (movimiento instanceof TransferenciaEmitida transferencia) {
dto = dtoTransferenciaEmitida(transferencia.getCuentaDestino().getNum(), movimiento.getImporte());
}
else if (movimiento instanceof TransferenciaRecibida transferencia) {
dto = dtoTransferenciaRecibida(transferencia.getCuentaOrigen().getNum(), movimiento.getImporte());
}
return dto;
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.rest.dto;
import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import java.time.LocalDate;
/**
* DTO para información de tarjetas bancarias
* @author ajrueda
*/
public record DTOTarjeta(
String num,
String titular,
LocalDate fechaCaducidad,
String cvc) {
public DTOTarjeta(Tarjeta tarjeta) {
this(tarjeta.getNum(), tarjeta.getTitular(), tarjeta.getFechaCaducidad(), tarjeta.getCvc());
}
public Tarjeta aTarjeta() {
return new Tarjeta(num, titular, fechaCaducidad, cvc);
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.seguridad;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.servicios.ServicioUjaCoin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/**
* Servicio que proporciona los datos del cliente
* @author ajrueda
*/
@Service
public class ServicioDatosCliente implements UserDetailsService {
@Autowired
ServicioUjaCoin servicioUjaCoin;
@Override
public UserDetails loadUserByUsername(String dni) throws UsernameNotFoundException {
Cliente cliente = servicioUjaCoin.verCliente(dni)
.orElseThrow(() -> new UsernameNotFoundException(""));
return User.withUsername(cliente.getDni())
.roles("CLIENTE").password(cliente.getClave())
.build();
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.seguridad;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
/**
* Proveedor de datos de seguridad de UJaCoin
*
* @author ajrueda
*/
@Configuration
public class ServicioSeguridadUjaCoin {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic(httpBasic -> httpBasic.realmName("ujacoin"))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.disable())
.authorizeHttpRequests(request -> request.requestMatchers(HttpMethod.POST, "/ujacoin/clientes")
.permitAll())
.authorizeHttpRequests(request -> request.requestMatchers("/ujacoin/clientes/{dni}/**")
.access(new WebExpressionAuthorizationManager("hasRole('CLIENTE') and #dni == principal.username")));
return httpSecurity.build();
}
/** Hacer visible el gestor de autenticación del sistema por si se quiere hacer login manual */
// @Bean
// public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfiguration) throws Exception {
// return authConfiguration.getAuthenticationManager();
// }
}
......@@ -25,6 +25,8 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
......@@ -44,6 +46,9 @@ public class ServicioUjaCoin {
@Autowired
RepositorioCuentas repositorioCuentas;
@Autowired
CacheManager cacheManager;
public ServicioUjaCoin() {
}
......@@ -53,12 +58,13 @@ public class ServicioUjaCoin {
* @return la cuenta asociada al cliente
*/
public Cuenta altaCliente(@NotNull @Valid Cliente cliente) {
if (repositorioClientes.buscar(cliente.getDni()).isPresent()) {
Optional<Cliente> test = repositorioClientes.buscar(cliente.getDni());
if (test.isPresent()) {
throw new ClienteYaRegistrado();
}
repositorioClientes.guardar(cliente);
// Crear y registrar cuenta
Cuenta cuenta = crearCuenta(cliente);
repositorioCuentas.guardar(cuenta);
......@@ -83,10 +89,26 @@ public class ServicioUjaCoin {
}
/**
* Realiza un login de un cliente
* @param dni el DNI del cliente
* @param clave la clave de acceso
* @return el objeto de la clase Cliente asociado
*/
@Transactional
public Optional<Cliente> verCliente(@NotBlank String dni) {
Optional<Cliente> clienteLogin = repositorioClientes.buscar(dni);
// Asegurarnos de que se devuelve el cliente con los datos precargados
clienteLogin.ifPresent(c -> c.verCuentas().size());
return clienteLogin;
}
/**
* Crear una cuenta adicional para el cliente
* @param dni el DNI delcliente
* @return la cuenta creada
*/
@CacheEvict(value="clientes", key = "#dni")
public Cuenta crearCuenta(@NotBlank String dni) {
Cliente cliente = repositorioClientes.buscar(dni).orElseThrow(ClienteNoRegistrado::new);
......@@ -102,6 +124,7 @@ public class ServicioUjaCoin {
* @param tarjeta la tarjeta a registrar
*/
// @Transactional
@CacheEvict(value="clientes", key = "#dni")
public void registrarTarjeta(@NotBlank String dni, @NotNull @Valid Tarjeta tarjeta) {
Cliente cliente = repositorioClientes.buscar(dni).orElseThrow(ClienteNoRegistrado::new);
cliente.verTarjeta(tarjeta.getNum()).ifPresent(x -> { throw new TarjetaYaRegistrada(); } );
......@@ -197,7 +220,7 @@ public class ServicioUjaCoin {
* @return la lista de los movimientos entre las fechas indicadas
*/
@Transactional
List<Movimiento> listarMovimientosCuentaDesdeHasta(@NotBlank String numCuenta, @Past LocalDateTime fechaHoraDesde, @Past LocalDateTime fechaHoraHasta) {
public List<Movimiento> listarMovimientosCuentaDesdeHasta(@NotBlank String numCuenta, @Past LocalDateTime fechaHoraDesde, @Past LocalDateTime fechaHoraHasta) {
Cuenta cuenta = repositorioCuentas.buscar(numCuenta)
.orElseThrow(CuentaNoRegistrada::new);
......
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package es.ujaen.dae.ujacoin.util;
import org.springframework.cache.Cache;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
*
* @author ajrueda
*/
public class CachedBCryptPasswordEncoder extends BCryptPasswordEncoder {
Cache cache;
public CachedBCryptPasswordEncoder(Cache cache) {
super();
this.cache = cache;
}
public CachedBCryptPasswordEncoder(Cache cache, int strength) {
super(strength);
this.cache = cache;
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
CharSequence cachedMatch = cache.get(encodedPassword, CharSequence.class);
if (cachedMatch != null && cachedMatch.equals(rawPassword)) {
return true;
}
boolean result = super.matches(rawPassword, encodedPassword);
if (result == true) {
cache.put(encodedPassword, rawPassword);
}
return result;
}
}
package es.ujaen.dae.ujacoin.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Codificador sencillo para contraseñas basado en Md5 (no seguro)
* @author ajrueda
*/
public class CodificadorMd5 {
public class CodificadorPassword {
static BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
private CodificadorMd5() {
private CodificadorPassword() {
}
public static String codificar(String cadena) {
/*
String cadenaCodificada = null;
try {
......@@ -25,6 +26,11 @@ public class CodificadorMd5 {
// No debe ocurrir puesto que MD5 es un algoritmo que existe en la
// implementación Java estándar
}
return cadenaCodificada;
*/
return encoder.encode(cadena);
}
public static boolean igual(String password, String passwordCodificado) {
return encoder.matches(password, passwordCodificado);
}
}
......@@ -5,4 +5,5 @@ spring.datasource.url: jdbc:h2:mem:ujacoin_test;MODE=MYSQL;DATABASE_TO_LOWER=TRU
spring.jpa.properties.javax.persistence.schema-generation.database.action: drop-and-create
spring.cache.type: NONE
## Fichero de configuración para UjaCoin
## MySQL local
spring.datasource.url: jdbc:mysql://localhost:33060/ujacoin
spring.datasource.username: ujacoin
spring.datasource.password: secret
spring.jpa.properties.javax.persistence.schema-generation.database.action: none
## H2 file
#spring.datasource.url: jdbc:h2:file:~/data/ujacoin;MODE=MYSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
spring.jpa.properties.javax.persistence.schema-generation.database.action: none
spring.data.jpa.repositories.bootstrap-mode: default
spring.jpa.open-in-view: false
spring.cache.type: NONE
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<cache name="clientes"
maxElementsInMemory="100"
eternal="false"
overflowToDisk="false" />
<cache name="claves"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
overflowToDisk="false" />
</ehcache>
\ No newline at end of file
package es.ujaen.dae.ujacoin.servicios;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
......@@ -9,6 +8,7 @@ import es.ujaen.dae.ujacoin.entidades.movimientos.TransferenciaEmitida;
import es.ujaen.dae.ujacoin.entidades.movimientos.TransferenciaRecibida;
import jakarta.validation.ConstraintViolationException;
import es.ujaen.dae.ujacoin.excepciones.SaldoInsuficienteParaOperacion;
import es.ujaen.dae.ujacoin.servicios.ServicioUjaCoin;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
......@@ -21,10 +21,13 @@ import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.MethodMode;
import org.springframework.test.context.ActiveProfiles;
/**
* Test de integración de la aplicación
* @author ajrueda
......@@ -37,6 +40,7 @@ public class ServicioUjaCoinTest {
@Autowired
ServicioUjaCoin servicioUjaCoin;
@Test
public void testAccesoServicioUjaCoin() {
Assertions.assertThat(servicioUjaCoin).isNotNull();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment