PostgreSQL con un poco de SpringBoot

Otro post más, se verá algo de bases de datos, springboot, java, xml, h2, flyway para la migracion de h2 a postgreSQL, securizar un poco en postgreSQL y listo, más de lo mismo.

Migrando Script de h2 a PostgreSQL

Migracion
  1. Paso 1 Habilitando flyway desde el pom.xml

  2. Paso 2 formateo del fichero para el versionado correcto de flyway

Propiedades para el h2

### spring config
spring:
  datasource:
    url: jdbc:h2:mem:db (1)
    username: test
    password: test
1db: es el nombre de nuestra bases de datos.

Depedencias para nuestro pom.xml

Ya estoy esta con un parent de spring y por lo tanto la version la hereda, por eso no esta el tag <version>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.6</version>
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
En esta versión de springboot 3.3.6 debe estar habilitado en nuestra seguridad la consola de h2, para poder acceder a ella al menos en desarrollo.
@Bean
public WebSecurityCustomizer ignoringCustomizer() {
    return (web) -> web.ignoring().requestMatchers("/h2-console/**");
}

Desde la UI cuando nos conectamos podemos hacer esto

Tips
  1. Claramente antes de acceder a la UI, debemos tener todas nuestras entidades creadas en java para el h2, y con compatibilidad tal cual para que puedan crearse ahi.

  2. Luego al acceder, podremos visualizar todas las tablas etc.

SCRIPT TO 'dump.sql' (1)
1Esto creará un fichero llamado dump.sql dentro el directorio del proyecto, no hace falta copiar y pegar 😚

h2 dump sql

Renombrado
  1. Renombrarlo a V1__init.sql

  2. Así lo reconocerá Flyway en el src/main/resources/db/migration

convension migration

El Docker compose

Necesitamos tener postgresql levantado al menos en local, o usar testcontainer.

services:
  postgres:
    image: 'postgres:latest'
    environment:
      - 'POSTGRES_DB=mydatabase'
      - 'POSTGRES_PASSWORD=secret'
      - 'POSTGRES_USER=myuser'
    ports:
      - '5432:5432'

Reconfigurando properties de Springboot

Flyway debe encargarse de la estructura de la base de datos, por lo que es importante desactivar la auto-creación de Hibernate.

### spring config
spring:
  jpa:
    defer-datasource-initialization: true
    show-sql: true
    generate-ddl: false (1)
    hibernate
        ddl-auto: none (2)
    properties:
      hibernate:
        format_sql: true
  #Config Flyway
  flyway:
    enabled: true
    baseline-on-migrate: true
    out-of-order: true
  datasource:
    url: jdbc:postgresql://localhost:5432/mydatabase
    username: myuser
    password: secret
    driverClassName: org.postgresql.Driver (3)
1desactivado.
2desactivado.
3Esta propiedad se supone que ya es opcional.

Esto evita que Hibernate intente modificar la base de datos antes de que Flyway haga su trabajo.

Actualizamos el pom.xml

Con estas dependencias del driver de postgresql y flyway

<!--<dependency>
    <groupId>com.h2database</groupId> (1)
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
    </dependency> -->

<!-- https://mvnrepository.com/artifact/org.flywaydb/flyway-core -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>11.3.0</version> (2)
</dependency>

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-postgresql</artifactId> (3)
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
1Dependencia de h2 comentada.
2Importante tenerla para evitar errores tipo Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: PostgreSQL 17.2
3Importante también.

Posible error en el arranque, con jpa y flyway

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Failed to initialize dependency 'flyway' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'flyway' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Circular depends-on relationship between 'flyway' and 'entityManagerFactory'

Lo más probable es que estés posponiendo la inicialización del Datasource agregando:

  • defer-datasource-initialization: false

Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: PostgreSQL 17.2
Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: PostgreSQL 15.10

Instalando PostgreSQL FreeBSD

Postgresql elephant

La wiki en nuestra amiga PostgreSQL/Setup

Un poco de postgres con springboot para securizar

La seguridad suele ser importante siempre se usar para la prueba la version de postgres 15, para ellos habilitamos rapidamente un jail con bastille usando la template por ejemplo esta

aplicandolo al jail existente llamado postgresql

bastille template TARGET yaazkal/bastille-postgres (1)
1TARGET es el nombre de nuestro jail

Solo con acceso desde localhost

Si, tengo solo habilitado usar el psql desde el propio server con esta linea en el fichero pg_hba.conf

# "local" is for Unix domain socket connections only
local   all             all                                     trust (1)
1las otra lineas por recomendación mejor que esten con scram-sha-256 por trust o MD5

Migrando password a SCRAM

El fichero postgresql.conf toca actualizar la línea

# - Authentication -

#authentication_timeout = 1min          # 1s-600s
password_encryption = scram-sha-256     # scram-sha-256 or md5 (1)
1Aqui descomentar y usar scram-sha-256
postgres=#
SELECT rolname, rolpassword ~ '^SCRAM-SHA-256\$' AS has_upgraded
FROM pg_authid
WHERE rolcanlogin;
 rolname  | has_upgraded
----------+--------------
 postgres |
 admin    | t (1)
(2 rows)
1Indica true, que ya esta actualizada

Posiblemente para autenticarnos desde nuestro cliente java con spring tengas ciertos problemas como

Caused by: org.postgresql.util.PSQLException: FATAL: password authentication failed for user "admin"

Actualizacion recomentada

$ psql
psql (14.15)
Type "help" for help.

postgres=# \password admin (1)
Enter new password for user "admin":
1Usando \password se hace la actualizacion correcta de las password 🔥
ALTER ROLE admin WITH PASSWORD 'tu_contraseña'; (1)
1Esto no es recomendado para la actualización, ademas de dejar rastro en el histórico.

Para reiniciar db

service postgresql reload

Cargando correctamente el .bashrc del IDE

Es importante para asegurar nos que nuestro ide carge el .bashrc correctamente donde tenemos las variables de entorno establecidas como en la imagen, es bastante util.

/bin/bash --login

load bashrc from intellIIdea

/home/rubn/.local/share/applications/IntelliJ.desktop
#!/usr/bin/env xdg-open
[Desktop Entry]
Type=Application
Name=IntelliJ
StartupWMClass=jetbrains-idea-ce
Exec=/bin/bash -i -c "/home/rubn/Downloads/jdk-ide/idea/bin/idea" %f (1)
Icon=/home/rubn/Downloads/jdk-ide/idea/bin/idea.svg
Categories=Network;Application;
1Aquí forzamos la carga de modo interactivo con el parametro -i

Cada cambio en dichero fichero le toca un reinicio con

El comando \du

Nos permite ver los roles con sus atributos

postgres=# \du
                                   List of roles
 Role name |                         Attributes                         | Member of
-----------+------------------------------------------------------------+-----------
 admin     | Create DB                                                  | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

Importanto Schemas

Para hacerlo podemos usar el comando

psql -U \ (1)
admin -d \ (2)
ott -a -f /var/db/postgres/ott-schema.sql (3)
1PostgreSQL en modo interactivo, con el parametro de -U de usuario.
2El comando -d para la database ott.
3El comando -a imprime todas la lineas, y -f es la ruta donde el usuario postgres tiene permisos de lectura, necesario para import dicho .sql.

Purpose: Run SQL script to create/modify database schema while showing each command.

Encoding para la password

Si usamos por defecto el bean JdbcUserDetailsManager por el InMemoryUserDetailsManager

@Bean
public JdbcUserDetailsManager jdbcUserDetailsManager(DataSource dataSource) {
    return new JdbcUserDetailsManager(dataSource);
}

Debemos añadir un encoder por defecto al menos

java.lang.IllegalArgumentException: Given that there is no default password encoder configured, each password must have a password encoding prefix. Please either prefix this password with '{noop}' or set a default password encoder in `DelegatingPasswordEncoder`.

En el hash por defecto de spring security es un SHA-256

username|password                                                                        |enabled|
--------+--------------------------------------------------------------------------------+-------+
boby    |843b103f6c09de407d366f8ff6553691767a35dbaf870cad47e86945c4103be85ee8ea49509b9502|true   |
chispa    |a94f80ed1a6677dedb489a6912e6c83a7c2a7ada2c4d739dd4a4ab9e454d3d875134fa4480b19c55|true   |

Actualizamos añadiendo un template del hash

UPDATE users SET password = '{sha256}' || password;
username|password                                                                                |enabled|
--------+----------------------------------------------------------------------------------------+-------+
boby    |{sha256}843b103f6c09de407d366f8ff6553691767a35dbaf870cad47e86945c4103be85ee8ea49509b9502|true   |
chispa  |{sha256}a94f80ed1a6677dedb489a6912e6c83a7c2a7ada2c4d739dd4a4ab9e454d3d875134fa4480b19c55|true   |

Si aplicamos este otro bean y nos logueamos en el login de spring security el password encoder se cambiara a bcrypt bajo fondo.

@Bean
public UserDetailsPasswordService userDetailsPasswordService(UserDetailsManager userDetailsManager) {
    return (user, newPassword) -> {
        var updated = User.withUserDetails(user)
                .password(newPassword)
                .build();
        userDetailsManager.updateUser(updated);
        return updated;
    };
}

Migración bajo fondo a bcrypt

username|password                                                                                |enabled|
--------+----------------------------------------------------------------------------------------+-------+
boby    |{sha256}843b103f6c09de407d366f8ff6553691767a35dbaf870cad47e86945c4103be85ee8ea49509b9502|true   |
chispa  |{bcrypt}$2a$10$dMqSFw875ZLvpsADb24XDezNMacIYqC4bIUkbb24QOLYE3pmlZInS                    |true   |

Ya ese bcrypt nos dice que fue un update existoso