Commit 14aed9e0 by Antonio Rueda

Revisión de imports y creación de caché para acelerar autenticacones

parent 08da552f
## 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>
......@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<version>2.6.1</version>
</parent>
<dependencies>
......@@ -31,23 +31,22 @@
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
......@@ -66,7 +65,29 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<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>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -3,19 +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",
@SpringBootApplication(scanBasePackages = {
"es.ujaen.dae.ujacoin.servicios",
"es.ujaen.dae.ujacoin.repositorios",
"es.ujaen.dae.ujacoin.controladoresREST",
"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);
......
......@@ -15,6 +15,7 @@ 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 java.time.LocalDateTime;
......@@ -59,7 +60,7 @@ public class ControladorREST {
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handlerClienteNoRegistrado(ClienteNoRegistrado e) {
}
/** Creación de clientes */
@PostMapping("/clientes")
ResponseEntity<DTOCuenta> altaCliente(@RequestBody DTOCliente cliente) {
......@@ -72,6 +73,12 @@ public class ControladorREST {
}
}
// Just for testing
@GetMapping("/clientes/hola")
String hola() {
return "Hola";
}
/** Login de clientes (temporal hasta incluir autenticación mediante Spring Security */
@GetMapping("/clientes/{dni}")
ResponseEntity<DTOCliente> verCliente(@PathVariable String dni) {
......@@ -164,6 +171,9 @@ public class ControladorREST {
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();
}
......
/*
/*
* 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.
......
......@@ -8,8 +8,11 @@ package es.ujaen.dae.ujacoin.repositorios;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import java.util.Optional;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
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 +27,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 +46,7 @@ public class RepositorioClientes {
cliente.nuevaTarjeta(tarjeta);
}
@CacheEvict(value="clientes", key = "#cliente.dni")
public void actualizar(Cliente cliente) {
em.merge(cliente);
}
......
......@@ -7,6 +7,8 @@ package es.ujaen.dae.ujacoin.seguridad;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.servicios.ServicioUjaCoin;
import java.util.Optional;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
......
......@@ -5,13 +5,15 @@
*/
package es.ujaen.dae.ujacoin.seguridad;
import es.ujaen.dae.ujacoin.util.CachedBCryptPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.config.http.SessionCreationPolicy;
/**
* Proveedor de datos de seguridad de UJaCoin
......@@ -23,10 +25,14 @@ public class ServicioSeguridadUjaCoin extends WebSecurityConfigurerAdapter {
@Autowired
ServicioDatosCliente servicioDatosCliente;
@Autowired
CacheManager cacheManager;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(servicioDatosCliente)
.passwordEncoder(new BCryptPasswordEncoder());
.passwordEncoder(new CachedBCryptPasswordEncoder(cacheManager.getCache("claves")));
//.passwordEncoder(new BCryptPasswordEncoder());
//auth.inMemoryAuthentication()
// .withUser("ujacoin").roles("CLIENTE").password("{noop}secret");
......@@ -34,13 +40,19 @@ public class ServicioSeguridadUjaCoin extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// Desactivar cualquier soporte de sesión
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.csrf().disable();
// Activar seguridad HTTP Basic
httpSecurity.httpBasic();
// httpSecurity.authorizeRequests().antMatchers("/ujacoin/**").permitAll();
// Definir protección por URL
httpSecurity.authorizeRequests().antMatchers(HttpMethod.POST, "/ujacoin/clientes").permitAll();
httpSecurity.authorizeRequests().antMatchers(HttpMethod.POST, "/ujacoin/clientes").anonymous();
// Permitir el acceso de un cliente sólo a sus recursos asociados (datos personales, cuentas, tarjetas, etc.)
httpSecurity.authorizeRequests().antMatchers("/ujacoin/clientes/{dni}/**")
.access("hasRole('CLIENTE') and #dni == principal.username");
httpSecurity.authorizeRequests().antMatchers("/ujacoin/clientes/{dni}/**").access("hasRole('CLIENTE') and #dni == principal.username");
}
}
......@@ -25,6 +25,8 @@ import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Positive;
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);
......@@ -102,6 +108,7 @@ public class ServicioUjaCoin {
* @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);
......@@ -117,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(); } );
......
/*
* 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;
}
}
......@@ -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
spring.datasource.url: jdbc:mysql://localhost:33060/ujacoin
spring.datasource.username: ujacoin
spring.datasource.password: secret
## MySQL local
# spring.datasource.url: jdbc:mysql://localhost:33060/ujacoin
# spring.datasource.username: ujacoin
# spring.datasource.password: secret
## H2 embedded
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: drop-and-create
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
......@@ -13,12 +13,14 @@ import java.time.LocalDate;
import java.util.List;
import javax.annotation.PostConstruct;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cache.CacheManager;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
......@@ -39,9 +41,9 @@ public class ControladorRESTTest {
@Autowired
MappingJackson2HttpMessageConverter springBootJacksonConverter;
TestRestTemplate restTemplate;
/**
* Crear un TestRestTemplate para las pruebas
*/
......@@ -53,7 +55,7 @@ public class ControladorRESTTest {
restTemplate = new TestRestTemplate(restTemplateBuilder);
}
/**
* Intento de creación de un cliente inválido
*/
......
......@@ -18,9 +18,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.CacheManager;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.MethodMode;
import org.springframework.test.context.ActiveProfiles;
......@@ -36,7 +38,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