Spring wiki - emizoripx/java-wiki GitHub Wiki

Table of Contents

Estereotipos de Spring

@Component

Esta anotación se usa en clases para indicar un componente Spring. La anotación @Component marca la clase Java como un bean o componente para que el mecanismo de exploración de componentes de Spring pueda agregarla al contexto de la aplicación.

@Controller

La anotación @Controller se usa para indicar que la clase es un controlador Spring. Esta anotación se puede utilizar para identificar controladores para Spring MVC o Spring WebFlux.

@Service

Esta anotación se usa en una clase. @Service marca una clase Java que realiza algún servicio, como ejecutar lógica de negocios, realizar cálculos y llamar a API externas. Esta anotación es una forma especializada de la anotación @Component destinada a ser utilizada en la capa de servicio.

@Repository

Esta anotación se utiliza en clases Java que acceden directamente a la base de datos. La anotación @Repository funciona como un marcador para cualquier clase que cumpla la función de repositorio u Objeto de acceso a datos.

Spring Handler Exceptions

Spring nos proporciona herramientas para el manejo de excepciones más allá de la simple 'try-catch' bloques. De las variadas herramientas que provee spring; con Spring boot repasaemos tres opciones: por excepción, por controlador o globalmente.

Manejo de exepciones basado en excepciones

Normalmente, cualquier excepción no controlada que se produzca al procesar una solicitud web hace que el servidor devuelva una respuesta HTTP 500. Sin embargo, cualquier excepción que escriba usted mismo puede anotarse con la anotación @ResponseStatus (que admite todos los códigos de estado HTTP definidos por la especificación HTTP). Cuando se genera una excepción anotada desde un método de controlador y no se maneja en otro lugar, automáticamente se devolverá la respuesta HTTP adecuada con el código de estado especificado.

Por ejemplo, aquí hay una excepción para un pedido faltante.

@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="No such Order")  // 404
public class OrderNotFoundException extends RuntimeException {
    public OrderNotFoundException(String orderId) {
        super(orderId + " not found");
    }
}

Y aquí hay un método de controlador que lo usa:

@RequestMapping(value="/orders/{id}", method= RequestMethod.GET)
public String showOrder(@PathVariable("id") long id, Model model) {
    logger.info("showOrder: byException");
    if (id == 0) throw new OrderNotFoundException(String.valueOf(id));

    model.addAttribute(id);
    return "orderDetail";
}

Se devolverá una respuesta HTTP 404 familiar si la URL manejada por este método incluye una identificación de pedido desconocida.

Manejo de excepciones basado en controlador

Usando @ExceptionHandler

Puede agregar métodos adicionales (@ExceptionHandler) a cualquier controlador para manejar específicamente las excepciones generadas por los métodos de manejo de solicitudes (@RequestMapping) en el mismo controlador. Estos métodos pueden:

  • Manejar excepciones sin la anotación @ResponseStatus (generalmente excepciones predefinidas que usted no escribió)
  • Redirigir al usuario a una vista de error dedicada
  • Cree una respuesta de error totalmente personalizada

El siguiente controlador demuestra estas tres opciones:

@Controller
@RequestMapping("/example2")
public class Example2Controller {
    private final Logger logger = LoggerFactory.getLogger(Example2Controller.class);

    @RequestMapping(value="/invoices/{id}", method= RequestMethod.GET)
    public String showInvoice(@PathVariable("id") long id) {
        logger.info("showInvoice :: by Controller");
        if (id == 0) throw new DataIntegrityViolationException(String.valueOf(id));

        return "invoiceDetail";
    }

    @RequestMapping(value="/orders/{id}", method= RequestMethod.GET)
    public String showOrder(@PathVariable("id") long id) {
        logger.info("showOrder");
        if (id == 0) throw new OrderSecondNotFoundException(String.valueOf(id));

        return "orderDetail";
    }

    // Exception handling methods

    // Convert a predefined exception to an HTTP Status code
    @ResponseStatus(value= HttpStatus.CONFLICT,
            reason="Data integrity violation")  // 409
    @ExceptionHandler(DataIntegrityViolationException.class)
//    public ResponseEntity<ErrorMessage> conflict() {
    public void conflict() {
        logger.info("conflict");
        // Nothing to do

//        ErrorMessage errorMessage = new ErrorMessage("Conflict");
//
//        return ResponseEntity.status(HttpStatus.CONFLICT).body(errorMessage);
    }

    // Specify name of a specific view that will be used to display the error:
    @ExceptionHandler({SQLException.class, DataAccessException.class})
    public String databaseError() {
        logger.info("databaseError");
        // Nothing to do.  Returns the logical view name of an error page, passed
        // to the view-resolver(s) in usual way.
        // Note that the exception is NOT available to this view (it is not added
        // to the model) but see "Extending ExceptionHandlerExceptionResolver"
        // below.
        return "databaseError";
    }

    // Total control - setup a model and return the view name yourself. Or
    // consider subclassing ExceptionHandlerExceptionResolver (see below).
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorMessage> handleError(HttpServletRequest req, Exception ex) {
        logger.error("handleError :: Request: " + req.getRequestURL() + " raised " + ex);

        ErrorMessage errorMessage = new ErrorMessage(ex.getMessage());

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorMessage);
    }

}

En cualquiera de estos métodos, puede optar por realizar un procesamiento adicional; el ejemplo más común es registrar la excepción.

Los métodos de controlador tienen firmas flexibles para que pueda pasar objetos obvios relacionados con servlets, como HttpServletRequest, HttpServletResponse, HttpSession y/o Principio.

Nota importante: el modelo no puede ser un parámetro de ningún método @ExceptionHandler. En su lugar, configure un modelo dentro del método usando ModelAndView como se muestra en handleError() arriba.

Manejo de excepciones globales

Usando clases @ControllerAdvice

Un consejo de controlador le permite utilizar exactamente las mismas técnicas de manejo de excepciones, pero aplicarlas en toda la aplicación, no solo en un controlador individual. Puedes considerarlos como un interceptor impulsado por anotaciones.

Cualquier clase anotada con @ControllerAdvice se convierte en un consejo de controlador y se admiten tres tipos de métodos:

Cualquiera de los controladores de excepciones que vio anteriormente se puede definir en una clase de asesoramiento de controlador, pero ahora se aplican a las excepciones lanzadas desde cualquier controlador. Aquí hay un ejemplo simple:

@ControllerAdvice
class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT)  // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}

Si desea tener un controlador predeterminado para cualquier excepción, existe un pequeño inconveniente. Debe asegurarse de que el marco maneje las excepciones anotadas. El código se ve así:

// Target all Controllers annotated with @RestController
//@ControllerAdvice(annotations = RestController.class)
// Target all Controllers within specific packages
//@ControllerAdvice("com.example.exception.demoex.controller")
// Target all Controllers assignable to specific classes
//@ControllerAdvice(assignableTypes = {Example3Controller.class})
@ControllerAdvice
public class GlobalControllerExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class);
    @ResponseStatus(HttpStatus.NOT_FOUND)  // 404
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<ErrorMessage> defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // Nothing to do
        logger.error("defaultErrorHandler :: Request: " + req.getRequestURL() + " raised " + e);

        ErrorMessage errorMessage = new ErrorMessage(e.getMessage());

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorMessage);
    }
}

Spring Cloud OpenFeign

Caracteristicas

  • Feign es un cliente REST declarativo
  • Facilita la escritura de clientes de servicios web
  • Tiene soporte de anotaciones conectable, incluidas anotaciones Feign y anotaciones JAX-RS
  • Admite codificadores y decodificadores conectables
  • Spring Cloud agrega soporte para anotaciones Spring MVC

Una gran ventaja de usar Feign es que no tenemos que escribir ningún código para llamar al servicio, aparte de una definición de interfaz.

Dependencias

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Además, necesitaremos agregar las dependencias de spring-cloud :

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

FeignClient

  • A continuación, debemos agregar @EnableFeignClients a nuestra clase principal:
package com.mcalvaro.springcloudopenfeign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class SpringCloudOpenfeignApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringCloudOpenfeignApplication.class, args);
	}

}

Con esta anotación, habilitamos el escaneo de componentes en busca de interfaces que declaren que son clientes de Feign. Luego declaramos un cliente Feign usando la anotación @FeignClient :

package com.mcalvaro.springcloudopenfeign.feign;

import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "JsonClient", url = "ttps://my-json-server.typicode.com")
public interface IMyJsonClient {
 
}
  • Además, dado que esta interfaz es un cliente de Feign, podemos usar las anotaciones de Spring Web para declarar las API a las que queremos comunicarnos.
    @GetMapping("posts")
    public ResponseEntity<List<PostDto>> getAllPosts() {

        return ResponseEntity.ok(myJsonClient.getAllPosts());
    }

Configuración

Ahora bien, es muy importante comprender que cada cliente de Feign se compone de un conjunto de componentes personalizables.

Spring Cloud crea un nuevo conjunto predeterminado bajo demanda para cada cliente nombrado utilizando la clase FeignClientsConfiguration que podemos personalizar como se explica en la siguiente sección.

  • Creamos una clase con la anotación @Configuration
package com.mcalvaro.springcloudopenfeign.feign.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class MyJsonClientConfiguration {
 
}
  • Lo pasamos @FeignClient
package com.mcalvaro.springcloudopenfeign.feign;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

import com.mcalvaro.springcloudopenfeign.dto.PostDto;
import com.mcalvaro.springcloudopenfeign.feign.config.MyJsonClientConfiguration;

@FeignClient(value = "JsonClient", url = "https://my-json-server.typicode.com", configuration = MyJsonClientConfiguration.class)
public interface IMyJsonClient {
 
    @GetMapping("typicode/demo/posts")
    List<PostDto> getAllPosts();
}

Spring Cloud OpenFeign proporciona los siguientes beans de forma predeterminada

  • Decoder ResponseEntityDecoder , que envuelve SpringDecoder , utilizado para decodificar la respuesta
  • Encoder SpringEncoder se utiliza para codificar RequestBody.
  • Logger Slf4jLogger es el registrador predeterminado utilizado por Feign.
  • Contract SpringMvcContract , que proporciona procesamiento de anotaciones.
  • Feign.Builder HystrixFeign.Builder se utiliza para construir los componentes.
  • Client LoadBalancerFeignClient o cliente Feign predeterminado.

Configuración de Beans Personalizados

Si queremos personalizar uno o más de estos beans , podemos anularlos creando una clase de Configuración , que luego agregamos a la anotación FeignClient :

package com.mcalvaro.springcloudopenfeign.feign.config;

import feign.okhttp.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyJsonClientConfiguration {
 
    @Bean
    public OkHttpClient client() {
        return new OkHttpClient();
    }
}

En este ejemplo, le decimos a Feign que use OkHttpClient en lugar del predeterminado para admitir HTTP/2.

Spring Cloud OpenFeign no proporciona los siguientes beans de forma predeterminada para fingir, pero aún así busca beans de estos tipos en el contexto de la aplicación para crear el cliente fingido:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory
  • QueryMapEncoder
  • Capability

Interceptores

Agregar interceptores es otra característica útil proporcionada por Feign.

Los interceptores pueden realizar una variedad de tareas implícitas, desde autenticación hasta logs, para cada solicitud/respuesta HTTP.

    @Bean
    public RequestInterceptor requestInterceptor() {

        return requestTemplate -> {
            requestTemplate.header("api-key", "Sxmvjafoairiqwhrpltureancmaqyreqq");
            requestTemplate.header("Accept", "application/json");
        };
    }

Usando BasicAuthRequestInterceptor

Alternativamente, podemos usar la clase BasicAuthRequestInterceptor que proporciona Spring Cloud OpenFeign:

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("username", "password");
    }

Ahora todas las solicitudes contendrán el encabezado de autenticación básico.

Soporte Hystrix

Feign es compatible con Hystrix , por lo que si lo hemos habilitado, podemos implementar el patrón fallback Con el patrón fallback, cuando falla una llamada de servicio remoto, en lugar de generar una excepción, el consumidor del servicio ejecutará una ruta de código alternativa para intentar llevar a cabo la acción por otros medios. Para lograr el objetivo, necesitamos habilitar Hystrix agregando feign.hystrix.enabled=true en el archivo de propiedades. Esto nos permite implementar métodos alternativos que se llaman cuando falla el servicio:

@Component
public class MyJsonClientFallback implements IMyJsonClient {

    private final Logger logger = LoggerFactory.getLogger(MyJsonClientFallback.class);

    @Override
    public List<PostDto> getAllPosts() {

        // TODO: Habilitar hytrix
        return Collections.emptyList();
    }

}

Logging

Para cada cliente de Feign, se crea un logger de forma predeterminada.

Para habilitar el log, debemos declararlo en el archivo application.propertie usando el nombre del paquete de las interfaces del cliente:

logging.level.com.baeldung.cloud.openfeign.client: DEBUG

O, si queremos habilitar el registro sólo para un cliente particular en un paquete, podemos usar el nombre completo de la clase:

logging.level.com.mcalvaro.cloud.openfeign.client.IMyJsonClient: DEBUG

Tenga en cuenta que el registro de Feign responde solo al nivel DEBUG .

El Logger.Level que podemos configurar por cliente indica cuánto registrar:

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }

Hay cuatro niveles de registro para elegir:

  • NONE sin registro, que es el valor predeterminado
  • BASIC registra solo el método de solicitud, la URL y el estado de la respuesta
  • HEADERS registra la información básica junto con los encabezados de solicitud y respuesta.
  • FULL registre el cuerpo, los encabezados y los metadatos tanto para la solicitud como para la respuesta

Manejo de errores

El controlador de errores predeterminado de Feign, ErrorDecoder.default , siempre arroja una FeignException .

Ahora bien, este comportamiento no siempre es el más útil. Entonces, para personalizar la excepción lanzada, podemos usar un CustomErrorDecoder :

public class CustomErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String method, Response response) {

        switch (response.status()) {

            case 400:
                return new BadRequestException("Bad Request");
            case 404:
                return new NotFoundException("Not Found");
            case 500:
                return new Exception("Internal Server Error");
            default:
                return new Exception();

        }

    }

}

Luego, como hicimos anteriormente, tenemos que reemplazar el ErrorDecoder predeterminado agregando un bean a la clase de Configuración :

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }

Spring Cloud Config

Spring Cloud Config proporciona soporte del lado del servidor y del lado del cliente para la configuración externalizada en un sistema distribuido. Los conceptos tanto en el cliente como en el servidor se asignan de manera idéntica a las abstracciones Spring Environment y PropertySource, por lo que encajan muy bien con las aplicaciones Spring pero se pueden usar con cualquier aplicación que se ejecute en cualquier idioma. A medida que una aplicación avanza por el proceso de implementación desde el desarrollo hasta la prueba y la producción, puede administrar la configuración entre esos entornos y estar seguro de que las aplicaciones tienen todo lo que necesitan para ejecutarse cuando se migran. La implementación predeterminada del backend servidor utiliza git, por lo que admite fácilmente versiones etiquetadas de entornos de configuración, además de ser accesible a una amplia gama de herramientas para administrar el contenido. Es fácil agregar implementaciones alternativas y conectarlas con la configuración de Spring.

Inicio

Servidor

La estrategia predeterminada para localizar fuentes de propiedades es clonar un repositorio git (en spring.cloud.config.server.git.uri) y usarlo para inicializar una mini SpringApplication. El entorno de la miniaplicación se utiliza para enumerar fuentes de propiedades y publicarlas en un punto final JSON.

El servidor es el que nos proveera de las configuraciones disponibles en cloud.

application.yml:

server:
  port: 8888
spring:
  application:
    name: foo
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo

Dependencias en pom.xml

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>

SpringCloudConfigDemoServerApplication.java:

package com.teamqr.springcloudconfigdemoserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class SpringCloudConfigDemoServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudConfigDemoServerApplication.class, args);
    }

}

El servicio HTTP tiene recursos de la siguiente forma:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

ejemplo:

curl localhost:8888/foo/development
curl localhost:8888/foo/development/master
curl localhost:8888/foo/development,db/master
curl localhost:8888/foo-development.yml
curl localhost:8888/foo-db.properties
curl localhost:8888/master/foo-db.properties

donde la application se inyecta como spring.config.name en SpringApplication (lo que normalmente es una application en una aplicación Spring Boot normal), el profile es el perfil activo (o una lista de propiedades separadas por comas) y label es una etiqueta git opcional ( El valor predeterminado es master or main.)

Cliente

El cliente se conectara al servidor para obtener las configuraciones que se requiere en la aplicación, para este caso en application.yml debe ir el siguiente contenido:

server:
  port: 8880
spring:
  application:
    name: foo
  profiles:
    active: dev
  config:
    import: optional:configserver:http://root:s3cr3t@localhost:8888

Dependencias en pom.xml

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-client</artifactId>
		</dependency>

SpringCloudConfigDemoClientApplication.java:

package com.teamqr.springcloudconfigdemoclient;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class SpringCloudConfigDemoClientApplication {
    
    @Value("${bar:default2}")
    private String value2;

    @RequestMapping("/view")
    public String pruebaValues() {
        return String.format("Value 2 es \"%s\"", value2 );
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudConfigDemoClientApplication.class, args);
    }

}

Spring Cloud Config Server

Spring Cloud Config Server proporciona una API basada en recursos HTTP para configuración externa (pares nombre-valor o contenido YAML equivalente). El servidor se puede integrar en una aplicación Spring Boot mediante la anotación @EnableConfigServer. En consecuencia, la siguiente aplicación es un servidor de configuración:

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
  public static void main(String[] args) {
    SpringApplication.run(ConfigServer.class, args);
  }
}

Como todas las aplicaciones Spring Boot, se ejecuta en el puerto 8080 de forma predeterminada, pero puedes cambiarlo al puerto más convencional 8888 de varias maneras. La más sencilla, que también establece un repositorio de configuración predeterminado, es iniciarlo con spring.config.name=configserver (hay un configserver.yml en el archivo jar del Config Server). Otra es utilizar sus propias propiedades de aplicación, como se muestra en el siguiente ejemplo:

server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo

donde ${user.home}/config-repo es un repositorio git que contiene archivos YAML y de propiedades.

Nota:

En Windows, necesita un "/" adicional en la URL del archivo si es absoluto con un prefijo de unidad (por ejemplo, file:///${user.home}/config-repo).

Advertencia:

La clonación inicial de su repositorio de configuración puede ser rápida y eficiente si solo guarda archivos de texto en él. Si almacena archivos binarios, especialmente los grandes, puede experimentar retrasos en la primera solicitud de configuración o encontrar errores de falta de memoria en el servidor.

Repositorio de entorno

¿Dónde debería almacenar los datos de configuración del Config Server? La estrategia que gobierna este comportamiento es EnvironmentRepository, que sirve a los objetos de Environment. Este entorno es una copia superficial del dominio del entorno Spring (incluido propertySources como característica principal). Los recursos del Medio Ambiente están parametrizados por tres variables:

  • {aplicación}, que se asigna a spring.application.name en el lado del cliente.
  • {profile}, que se asigna a spring.profiles.active en el cliente (lista separada por comas).
  • {label}, que es una característica del lado del servidor que etiqueta un conjunto "versionado" de archivos de configuración.

Las implementaciones de repositorio generalmente se comportan como una aplicación Spring Boot, cargando archivos de configuración desde spring.config.name igual al parámetro {application} y spring.profiles.active igual al parámetro {profiles}. Las reglas de precedencia para los perfiles también son las mismas que en una aplicación Spring Boot normal: los perfiles activos tienen prioridad sobre los predeterminados y, si hay varios perfiles, el último gana (similar a agregar entradas a un mapa).

Git Backend

La implementación predeterminada de EnvironmentRepository utiliza un backend Git, que es muy conveniente para administrar actualizaciones y entornos físicos y para auditar cambios. Para cambiar la ubicación del repositorio, puede configurar la propiedad de configuración spring.cloud.config.server.git.uri en Config Server (por ejemplo, en application.yml). Si lo configura con un prefijo file:, debería funcionar desde un repositorio local para que pueda comenzar rápida y fácilmente sin un servidor. Sin embargo, en ese caso, el servidor opera directamente en el repositorio local sin clonarlo (no importa si no está vacío porque el Config Server nunca realiza cambios en el repositorio "remoto"). Para ampliar el Config Server y hacerlo altamente disponible, necesita que todas las instancias del servidor apunten al mismo repositorio, de modo que solo funcione un sistema de archivos compartido. Incluso en ese caso, es mejor usar el protocolo ssh: para un repositorio de sistema de archivos compartido, de modo que el servidor pueda clonarlo y usar una copia de trabajo local como caché.

Esta implementación de repositorio asigna el parámetro {label} del recurso HTTP a una etiqueta git (commit id, branch name, o tag). Si la rama de git o el nombre de la etiqueta contiene una barra diagonal (/), entonces la etiqueta en la URL HTTP debe especificarse con la cadena especial () (para evitar ambigüedades con otras rutas URL). Por ejemplo, si la etiqueta es foo/bar, reemplazar la barra diagonal dará como resultado la siguiente etiqueta: foo()bar. La inclusión de la cadena especial (_) también se puede aplicar al parámetro {aplicación}. Si utiliza un cliente de línea de comandos como curl, tenga cuidado con los corchetes en la URL; debe escaparlos del shell con comillas simples ('').

Saltarse la validación del certificado SSL

La validación del servidor de configuración del certificado SSL del servidor Git se puede deshabilitar estableciendo la propiedad git.skipSslValidation en verdadero (el valor predeterminado es falso).

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          skipSslValidation: true

Setting HTTP Connection Timeout

Puede configurar el tiempo, en segundos, que el servidor de configuración esperará para adquirir una conexión HTTP. Utilice la propiedad git.timeout (el valor predeterminado es 5).

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          timeout: 4

Placeholders en Git URI

Spring Cloud Config Server admite una URL de repositorio git con marcadores de posición para {application} y {profile} (y {label} si lo necesita, pero recuerde que la etiqueta se aplica como una etiqueta git de todos modos). Por lo tanto, puede admitir una política de "un repositorio por aplicación" utilizando una estructura similar a la siguiente:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/teamqr/{application}

También puede admitir una política de "un repositorio por perfil" utilizando un patrón similar pero con {profile}.

Además, el uso de la cadena especial "(_)" dentro de los parámetros de {aplicación} puede habilitar la compatibilidad con varias organizaciones, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/{application}

Coincidencia de patrones y repositorios múltiples

Spring Cloud Config también incluye soporte para requisitos más complejos con coincidencia de patrones en la aplicación y el nombre del perfil. El formato del patrón es una lista separada por comas de nombres de {aplicación}/{perfil} con comodines (tenga en cuenta que es posible que sea necesario citar un patrón que comienza con un comodín), como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          repos:
            simple: file://${user.home}/repos/simple
            special:
              pattern: special*/dev*,*special*/dev*
              uri: file://${user.home}/repos/special
            local:
              pattern: local*
              uri: file://${user.home}/repos/local

Si {application}/{profile} no coincide con ninguno de los patrones, utiliza el URI predeterminado definido en spring.cloud.config.server.git.uri. En el ejemplo anterior, para el repositorio "simple", el patrón es simple/* (solo coincide con una aplicación denominada simple en todos los perfiles). El repositorio "local" coincide con todos los nombres de aplicaciones que comienzan con local en todos los perfiles (el sufijo /* se agrega automáticamente a cualquier patrón que no tenga un comparador de perfiles).

Nota:

El atajo de “una sola línea” utilizado en el ejemplo “simple” sólo se puede utilizar si la única propiedad que se establecerá es el URI. Si necesita configurar algo más (credenciales, patrón, etc.), debe utilizar el formulario completo.

La propiedad de patrón en el repositorio es en realidad una matriz, por lo que puede usar una matriz YAML (o sufijos [0], [1], etc. en archivos de propiedades) para vincular múltiples patrones. Es posible que deba hacerlo si va a ejecutar aplicaciones con múltiples perfiles, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/simple/config-repo
          repos:
            development:
              pattern:
                - '*/development'
                - '*/staging'
              uri: file://${user.home}/repos/development
            staging:
              pattern:
                - '*/qa'
                - '*/production'
              uri: file://${user.home}/repos/staging

Nota:

Spring Cloud guesses that a pattern containing a profile that does not end in * implies that you actually want to match a list of profiles starting with this pattern (so /staging is a shortcut for ["/staging", "/staging,"], and so on). This is common where, for instance, you need to run applications in the “development” profile locally but also the “cloud” profile remotely.

Opcionalmente, cada repositorio también puede almacenar archivos de configuración en subdirectorios, y los patrones para buscar esos directorios se pueden especificar como rutas de búsqueda. El siguiente ejemplo muestra un archivo de configuración en el nivel superior:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          search-paths:
            - foo
            - bar*

En el ejemplo anterior, el servidor busca archivos de configuración en el nivel superior y en el subdirectorio foo/ y también en cualquier subdirectorio cuyo nombre comience con bar.

De forma predeterminada, el servidor clona repositorios remotos cuando se solicita la configuración por primera vez. El servidor se puede configurar para clonar los repositorios al inicio, como se muestra en el siguiente ejemplo de nivel superior:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://git/common/config-repo.git
          repos:
            team-a:
                pattern: team-a-*
                cloneOnStart: true
                uri: https://git/team-a/config-repo.git
            team-b:
                pattern: team-b-*
                cloneOnStart: false
                uri: https://git/team-b/config-repo.git
            team-c:
                pattern: team-c-*
                uri: https://git/team-a/config-repo.git

En el ejemplo anterior, el servidor clona el repositorio de configuración del equipo a al inicio, antes de aceptar cualquier solicitud. Todos los demás repositorios no se clonan hasta que se solicita la configuración del repositorio.

Nota:

Configurar un repositorio para que se clone cuando se inicia Config Server puede ayudar a identificar rápidamente una fuente de configuración mal configurada (como un URI de repositorio no válido), mientras se inicia Config Server. Con cloneOnStart no habilitado para una fuente de configuración, Config Server puede iniciarse exitosamente con una fuente de configuración mal configurada o no válida y no detectar un error hasta que una aplicación solicite la configuración de esa fuente de configuración.

Autenticación

Para utilizar la autenticación básica HTTP en el repositorio remoto, agregue las propiedades de nombre de usuario y contraseña por separado (no en la URL), como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          username: trolley
          password: strongpassword

Authentication con AWS CodeCommit

Spring Cloud Config Server también admite la autenticación AWS CodeCommit. AWS CodeCommit utiliza un asistente de autenticación cuando utiliza Git desde la línea de comandos. Este asistente no se utiliza con la biblioteca JGit, por lo que se crea un JGit CredentialProvider para AWS CodeCommit si el URI de Git coincide con el patrón de AWS CodeCommit. Los URI de AWS CodeCommit siguen este patrón:

uri: https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/${repo}

Configuración de Git SSH usando propiedades

De forma predeterminada, la biblioteca JGit utilizada por Spring Cloud Config Server usa archivos de configuración SSH como ~/.ssh/known_hosts y /etc/ssh/ssh_config cuando se conecta a repositorios Git mediante un URI SSH. En entornos de nube como Cloud Foundry, el sistema de archivos local puede ser efímero o no ser fácilmente accesible. Para esos casos, la configuración SSH se puede establecer mediante propiedades de Java. Para activar la configuración SSH basada en propiedades, la propiedad spring.cloud.config.server.git.ignoreLocalSshSettings debe establecerse en verdadero, como se muestra en el siguiente ejemplo:

  spring:
    cloud:
      config:
        server:
          git:
            uri: [email protected]:team/repo1.git
            ignoreLocalSshSettings: true
            hostKey: someHostKey
            hostKeyAlgorithm: ssh-rsa
            privateKey: |
                         -----BEGIN RSA PRIVATE KEY-----
                         MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX
                         IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF
                         ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud
                         1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i
                         oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W
                         DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd
                         fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b
                         BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG
                         EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj
                         5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8
                         +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe
                         pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG
                         ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ
                         xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW
                         dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi
                         PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX
                         VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z
                         FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R
                         gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4
                         VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV
                         cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee
                         KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N
                         CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs
                         q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J
                         69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT
                         -----END RSA PRIVATE KEY-----

Eliminar ramas sin seguimiento en repositorios Git

Como Spring Cloud Config Server tiene un clon del repositorio git remoto después de verificar la rama en el repositorio local (por ejemplo, buscar propiedades por etiqueta), mantendrá esta rama para siempre o hasta el próximo reinicio del servidor (lo que crea un nuevo repositorio local). Por lo tanto, podría darse el caso de que se elimine la rama remota pero la copia local todavía esté disponible para recuperarse. Y si el servicio de cliente Spring Cloud Config Server comienza con --spring.cloud.config.label=deletedRemoteBranch,master, obtendrá propiedades de la rama local eliminadoRemoteBranch, pero no de master.

Para mantener las ramas del repositorio local limpias y actualizadas, se puede configurar la propiedad deleteUntrackedBranches. Hará que Spring Cloud Config Server fuerce la eliminación de ramas sin seguimiento del repositorio local. Ejemplo:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          deleteUntrackedBranches: true

Frecuencia de actualización de Git

Puede controlar la frecuencia con la que el servidor de configuración obtendrá datos de configuración actualizados de su backend de Git utilizando spring.cloud.config.server.git.refreshRate. El valor de esta propiedad se especifica en segundos. De forma predeterminada, el valor es 0, lo que significa que el servidor de configuración obtendrá la configuración actualizada del repositorio de Git cada vez que se solicite.

Etiqueta por defecto

La etiqueta predeterminada utilizada para Git es main. Si no configura spring.cloud.config.server.git.defaultLabel y no existe una rama llamada main, el servidor de configuración también intentará verificar una rama llamada master de manera predeterminada. Si desea deshabilitar el comportamiento de la rama alternativa, puede configurar spring.cloud.config.server.git.tryMasterBranch en falso.

Vault Backend

Spring Cloud Config Server también admite Vault como backend.

Vault es una herramienta para acceder a secretos de forma segura. Un secreto es cualquier cosa a la que desee controlar estrictamente el acceso, como claves API, contraseñas, certificados y otra información confidencial. Vault proporciona una interfaz unificada para cualquier secreto al mismo tiempo que proporciona un estricto control de acceso y registra un registro de auditoría detallado.

By default, Spring Cloud Config Server uses Token based Authentication to fetch config from Vault. Vault also supports additional authentication methods like AppRole, LDAP, JWT, CloudFoundry, Kubernetes Auth. In order to use any authentication method other than TOKEN or the X-Config-Token header, we need to have Spring Vault Core on the classpath so that Config Server can delegate authentication to that library. Please add the below dependencies to your Config Server App.

Maven (pom.xml)

<dependencies>
	<dependency>
		<groupId>org.springframework.vault</groupId>
		<artifactId>spring-vault-core</artifactId>
	</dependency>
</dependencies>
Gradle (build.gradle)

dependencies {
implementation "org.springframework.vault:spring-vault-core"
}

Compartir configuración con todas las aplicaciones

  • File Based Repositories
  • Vault Server
  • CredHub Server
  • AWS Secrets Manager
  • AWS Parameter Store

File Based Repositories

Con los repositorios basados en archivos (git, svn y nativos), los recursos con nombres de archivos en aplicación* (application.properties, application.yml, application-*.properties, etc.) se comparten entre todas las aplicaciones cliente. Puede utilizar recursos con estos nombres de archivos para configurar los valores predeterminados globales y hacer que los archivos específicos de la aplicación los anulen según sea necesario.

La función de anulación de propiedades también se puede utilizar para establecer valores predeterminados globales, y las aplicaciones de marcadores de posición pueden anularlos localmente.

Vault Server

Cuando utiliza Vault como backend, puede compartir la configuración con todas las aplicaciones colocando la configuración en secreto/aplicación. Por ejemplo, si ejecuta el siguiente comando de Vault, todas las aplicaciones que utilicen el servidor de configuración tendrán las propiedades foo y baz disponibles:

vault write secret/application foo=bar baz=bam

CredHub Server

Cuando usa CredHub como backend, puede compartir la configuración con todas las aplicaciones colocando la configuración en /aplicación/ o colocándola en el perfil predeterminado de la aplicación. Por ejemplo, si ejecuta el siguiente comando CredHub, todas las aplicaciones que utilicen el servidor de configuración tendrán las propiedades compartidas.color1 y compartida.color2 disponibles:

credhub set --name "/application/profile/master/shared" --type=json
value: {"shared.color1": "blue", "shared.color": "red"}

AWS Secrets Manager

Cuando utiliza AWS Secrets Manager como backend, puede compartir la configuración con todas las aplicaciones colocando la configuración en /application/ o colocándola en el perfil predeterminado de la aplicación. Por ejemplo, si agrega secretos con las siguientes claves, todas las aplicaciones que utilicen el servidor de configuración tendrán las propiedades compartidas.foo y compartidas.bar disponibles:

secret name = /secret/application/
secret value =
{
 shared.foo: foo,
 shared.bar: bar
}

Utilice la propiedad spring.cloud.config.server.aws-secretsmanager.default-label para establecer la etiqueta predeterminada. Si la propiedad no está definida, el backend utiliza AWSCURRENT como etiqueta provisional.

spring:
  profiles:
    active: aws-secretsmanager
  cloud:
    config:
      server:
        aws-secretsmanager:
          region: us-east-1
          default-label: 1.0.0

AWS Parameter Store

Cuando utiliza AWS Parameter Store como backend, puede compartir la configuración con todas las aplicaciones colocando propiedades dentro de la jerarquía /application.

JDBC Backend

Spring Cloud Config Server admite JDBC (base de datos relacional) como backend para las propiedades de configuración. Puede habilitar esta característica agregando spring-boot-starter-data-jdbc al classpath y usando el perfil jdbc o agregando un bean de tipo JdbcEnvironmentRepository. Si incluye las dependencias correctas en el classpath (consulte la guía del usuario para obtener más detalles al respecto), Spring Boot configura una fuente de datos. Por ejemplo: application.properties

server.port=8888
management.endpoints.web.exposure.include=*

spring.datasource.url=jdbc:mysql://localhost:3306/configdb
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true

bootstrap.yml

spring:
  application:
    name: config-server-with-jdbc
  profiles:
    active: jdbc
  cloud:
    config:
      server:
        jdbc:
          sql: SELECT PROP_KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
          order: 1

Tabla

create table PROPERTIES (
  id serial primary key, 
  CREATED_ON timestamp ,
  APPLICATION text, 
  PROFILE text, 
  LABEL text, 
  PROP_KEY text, 
  VALUE text
 );

Redis Backend

Spring Cloud Config Server admite Redis como backend para las propiedades de configuración. Puede habilitar esta función agregando una dependencia a Spring Data Redis. dependencia para pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>
</dependencies>

La siguiente configuración utiliza Spring Data RedisTemplate para acceder a Redis. Podemos usar las propiedades spring.redis.* para anular la configuración de conexión predeterminada.

spring:
  profiles:
    active: redis
  redis:
    host: redis
    port: 16379

AWS S3 Backend

Spring Cloud Config Server admite AWS S3 como backend para las propiedades de configuración. Puede habilitar esta característica agregando una dependencia al AWS Java SDK para Amazon S3.

<dependencies>
	<dependency>
		<groupId>software.amazon.awssdk</groupId>
		<artifactId>s3</artifactId>
	</dependency>
</dependencies>

La siguiente configuración utiliza el cliente AWS S3 para acceder a los archivos de configuración. Podemos usar las propiedades spring.cloud.config.server.awss3.* para seleccionar el depósito donde se almacena su configuración.

spring:
  profiles:
    active: awss3
  cloud:
    config:
      server:
        awss3:
          region: us-east-1
          bucket: bucket1

AWS Parameter Store Backend

Spring Cloud Config Server admite AWS Parameter Store como backend para las propiedades de configuración. Puede habilitar esta característica agregando una dependencia al AWS Java SDK para SSM.

Dependencia para pom

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>ssm</artifactId>
</dependency>

La siguiente configuración utiliza el cliente AWS SSM para acceder a los parámetros.

spring:
  profiles:
    active: awsparamstore
  cloud:
    config:
      server:
        awsparamstore:
          region: us-east-1
          endpoint: https://ssm.us-east-1.amazonaws.com
          origin: aws:parameter:
          prefix: /config/service
          profile-separator: _
          recursive: true
          decrypt-values: true
          max-results: 5

AWS Secrets Manager Backend

Spring Cloud Config Server admite AWS Secrets Manager como backend para las propiedades de configuración. Puede habilitar esta característica agregando una dependencia a AWS Java SDK para Secrets Manager.

Dependias en pom

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>secretsmanager</artifactId>
</dependency>

La siguiente configuración utiliza el cliente de AWS Secrets Manager para acceder a los secretos.

spring:
  profiles:
  	active: awssecretsmanager
  cloud:
    config:
      server:
        aws-secretsmanager:
          region: us-east-1
          endpoint: https://us-east-1.console.aws.amazon.com/
          origin: aws:secrets:
          prefix: /secret/foo
          profileSeparator: _

Repositorios de entornos compuestos

En algunos escenarios, es posible que desee extraer datos de configuración de múltiples repositorios de entorno. Para hacerlo, puede habilitar el perfil compuesto en las propiedades de la aplicación de su servidor de configuración o en el archivo YAML. Si, por ejemplo, desea extraer datos de configuración de un repositorio de Subversion, así como de dos repositorios de Git, puede establecer las siguientes propiedades para su servidor de configuración:

spring:
  profiles:
    active: composite
  cloud:
    config:
      server:
        composite:
        -
          type: svn
          uri: file:///path/to/svn/repo
        -
          type: git
          uri: file:///path/to/rex/git/repo
        -
          type: git
          uri: file:///path/to/walter/git/repo

Si desea extraer datos de configuración solo de repositorios que sean de distintos tipos, puede habilitar los perfiles correspondientes, en lugar del perfil compuesto, en las propiedades de la aplicación de su servidor de configuración o en el archivo YAML. Si, por ejemplo, desea extraer datos de configuración de un único repositorio Git y un único servidor HashiCorp Vault, puede establecer las siguientes propiedades para su servidor de configuración:

spring:
  profiles:
    active: git, vault
  cloud:
    config:
      server:
        git:
          uri: file:///path/to/git/repo
          order: 2
        vault:
          host: 127.0.0.1
          port: 8200
          order: 1

Anulaciones de propiedades

El servidor de configuración tiene una función de "overrides" que permite al operador proporcionar propiedades de configuración a todas las aplicaciones. La aplicación no puede cambiar accidentalmente las propiedades anuladas con los ganchos Spring Boot normales. Para declarar anulaciones, agregue un mapa de pares nombre-valor a spring.cloud.config.server.overrides, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        overrides:
          foo: bar

Los ejemplos anteriores hacen que todas las aplicaciones que son clientes de configuración lean foo=bar, independientemente de su propia configuración.

Indicador de salud

Config Server viene con un indicador de estado que verifica si el EnvironmentRepository configurado está funcionando. De forma predeterminada, solicita a EnvironmentRepository una aplicación denominada aplicación, el perfil predeterminado y la etiqueta predeterminada proporcionada por la implementación de EnvironmentRepository.

Puede configurar el indicador de estado para verificar más aplicaciones junto con perfiles y etiquetas personalizados, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      server:
        health:
          repositories:
            myservice:
              label: mylabel
            myservice-dev:
              name: myservice
              profiles: development

Puede desactivar el indicador de estado configurando management.health.config.enabled=false.

Además, puede proporcionar su propio estado de inactividad personalizado configurando la propiedad spring.cloud.config.server.health.down-health-status (valorada en "DOWN" de forma predeterminada).

Seguridad

Puede proteger su Config Server de cualquier forma que tenga sentido para usted (desde seguridad de red física hasta tokens portadores de OAuth2), porque Spring Security y Spring Boot ofrecen soporte para muchas disposiciones de seguridad.

Para utilizar la seguridad HTTP básica configurada por Spring Boot de forma predeterminada, incluya Spring Security en la ruta de clase (por ejemplo, a través de spring-boot-starter-security). El valor predeterminado es un nombre de usuario de usuario y una contraseña generada aleatoriamente. Una contraseña aleatoria no es útil en la práctica, por lo que le recomendamos configurar la contraseña (estableciendo spring.security.user.password) y cifrarla (consulte las instrucciones a continuación sobre cómo hacerlo).

Cifrado y descifrado

Si las fuentes de propiedades remotas contienen contenido cifrado (valores que comienzan con {cipher}), se descifran antes de enviarlos a los clientes a través de HTTP. La principal ventaja de esta configuración es que los valores de las propiedades no necesitan estar en texto plano cuando están "en reposo" (por ejemplo, en un repositorio git). Si un valor no se puede descifrar, se elimina del origen de la propiedad y se agrega una propiedad adicional con la misma clave pero con el prefijo no válido y un valor que significa "no aplicable" (normalmente <n/a>). Esto es en gran medida para evitar que el texto cifrado se utilice como contraseña y se filtre accidentalmente.

Si configura un repositorio de configuración remoto para aplicaciones cliente de configuración, es posible que contenga un archivo application.yml similar al siguiente:

spring:
  datasource:
    username: dbuser
    password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'

Los valores cifrados en el archivo application.properties no deben estar entre comillas. De lo contrario, el valor no se descifra. El siguiente ejemplo muestra valores que funcionarían:

spring.datasource.username: dbuser
spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ

Puede enviar este texto sin formato de forma segura a un repositorio git compartido y la contraseña secreta permanecerá protegida.

El servidor también expone los puntos finales /encrypt y /decrypt (suponiendo que estén protegidos y solo accedan a ellos agentes autorizados). Si edita un archivo de configuración remoto, puede usar el servidor de configuración para cifrar valores mediante la publicación en el punto final /encrypt, como se muestra en el siguiente ejemplo:

curl localhost:8888/encrypt -s -d mysecret

Gestión de claves

El Config Server puede utilizar una clave simétrica (compartida) o una asimétrica (par de claves RSA). La opción asimétrica es superior en términos de seguridad, pero a menudo es más conveniente usar una clave simétrica ya que es un valor de propiedad único para configurar en application.properties.

Para configurar una clave simétrica, debe establecer encrypt.key en una cadena secreta (o usar la variable de entorno ENCRYPT_KEY para mantenerla fuera de los archivos de configuración de texto sin formato).

Nota

Si incluye spring-cloud-starter-bootstrap en el classpath o establece spring.cloud.bootstrap.enabled=true como propiedad del sistema, deberá configurar encrypt.key en bootstrap.properties.

No puede configurar una clave asimétrica usando encrypt.key.

Para configurar una clave asimétrica, utilice un almacén de claves (por ejemplo, el creado por la utilidad keytool que viene con el JDK). Las propiedades del almacén de claves son encrypt.keyStore.* con * igual a:

Propiedad Descripción
encrypt.keyStore.location Contiene una ubicación del recurso
encrypt.keyStore.password Contiene la contraseña que desbloquea el almacén de claves.
encrypt.keyStore.alias Identifica qué clave de la tienda utilizar
encrypt.keyStore.type El tipo de KeyStore que se va a crear. El valor predeterminado es jks.

Crear un almacén de claves para pruebas

Para crear un almacén de claves para realizar pruebas, puede utilizar un comando similar al siguiente:

keytool -genkeypair -alias mytestkey -keyalg RSA -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" -keypass changeme -keystore server.jks -storepass letmein

Coloque el archivo server.jks en el classpath (por ejemplo) y luego, en su bootstrap.yml, para el servidor de configuración, cree las siguientes configuraciones:

encrypt:
  keyStore:
    location: classpath:/server.jks
    password: letmein
    alias: mytestkey
    secret: changeme

Sirviendo propiedades cifradas

A veces desea que los clientes descifren la configuración localmente, en lugar de hacerlo en el servidor. En ese caso, si proporciona la configuración encrypt.* para localizar una clave, aún puede tener los puntos finales /encrypt y /decrypt, pero debe desactivar explícitamente el descifrado de las propiedades salientes colocando spring.cloud.config.server. encrypt.enabled=false en bootstrap.[yml|properties]. Si no le importan los puntos finales, debería funcionar si no configura la clave o el indicador habilitado.

Notificaciones push y Spring Cloud Bus

Muchos proveedores de repositorios de código fuente (como Github, Gitlab, Gitea, Gitee, Gogs o Bitbucket) le notifican los cambios en un repositorio a través de un webhook. Puede configurar el webhook a través de la interfaz de usuario del proveedor como una URL y un conjunto de eventos en los que esté interesado. Por ejemplo, Github usa un POST para el webhook con un cuerpo JSON que contiene una lista de confirmaciones y un encabezado (X-Github-Event) configurado para enviar. Si agrega una dependencia en la biblioteca spring-cloud-config-monitor y activa Spring Cloud Bus en su servidor de configuración, entonces se habilita un punto final /monitor.

Cuando se activa el webhook, el servidor de configuración envía un RefreshRemoteApplicationEvent dirigido a las aplicaciones que cree que podrían haber cambiado. La detección de cambios se puede diseñar estratégicamente. Sin embargo, de forma predeterminada, busca cambios en los archivos que coinciden con el nombre de la aplicación (por ejemplo, foo.properties está dirigido a la aplicación foo, mientras que application.properties está dirigido a todas las aplicaciones). La estrategia a utilizar cuando desee anular el comportamiento es PropertyPathNotificationExtractor, que acepta los encabezados y el cuerpo de la solicitud como parámetros y devuelve una lista de rutas de archivo que cambiaron.

La configuración predeterminada funciona de inmediato con Github, Gitlab, Gitea, Gitee, Gogs o Bitbucket. Además de las notificaciones JSON de Github, Gitlab, Gitee o Bitbucket, puede activar una notificación de cambio PUBLICANDO en /monitor con parámetros de cuerpo codificados en forma en el patrón de ruta={aplicación}. Al hacerlo, se transmite a aplicaciones que coinciden con el patrón {application} (que puede contener comodines).

Spring Cloud Config Client

Una aplicación Spring Boot puede aprovechar inmediatamente Spring Config Server (u otras fuentes de propiedades externas proporcionadas por el desarrollador de la aplicación). También recoge algunas características útiles adicionales relacionadas con eventos de cambio de entorno.

Importación de datos de configuración de Spring Boot

Spring Boot 2.4 introdujo una nueva forma de importar datos de configuración a través de la propiedad spring.config.import. Esta es ahora la forma predeterminada de vincularse al Config Server.

Para conectarse opcionalmente al servidor de configuración, configure lo siguiente en application.properties:

spring.config.import=optional:configserver:[url config server]

Esto se conectará al servidor de configuración en la ubicación predeterminada de "http://localhost:8888". La eliminación del prefijo opcional: hará que el Config Client falle si no puede conectarse al Config Server.

Spring Boot Config Data resuelve la configuración en un proceso de dos pasos. Primero carga toda la configuración usando el perfil predeterminado. Esto permite que Spring Boot recopile toda la configuración que puede activar cualquier perfil adicional. Una vez que haya recopilado todos los perfiles activados, cargará cualquier configuración adicional para los perfiles activos. Debido a esto, es posible que vea que se realizan varias solicitudes al servidor de configuración de Spring Cloud para recuperar la configuración. Esto es normal y es un efecto secundario de cómo Spring Boot carga la configuración cuando se usa spring.config.import. En versiones anteriores de Spring Cloud Config, solo se realizaba una solicitud, pero esto significaba que no se podían activar perfiles desde la configuración proveniente del servidor de configuración. La solicitud adicional con solo el perfil "predeterminado" ahora lo hace posible.

Configurar el primer arranque

Para utilizar la forma de arranque heredada de conectarse a Config Server, el arranque debe habilitarse mediante una propiedad o el iniciador spring-cloud-starter-bootstrap. La propiedad es spring.cloud.bootstrap.enabled=true. Debe establecerse como una propiedad del sistema o una variable de entorno. Una vez que se haya habilitado el arranque, cualquier aplicación con Spring Cloud Config Client en el classpath se conectará al Config Server de la siguiente manera: cuando se inicia un cliente de configuración, se vincula al Config Server (a través de la propiedad de configuración de arranque spring.cloud.config.uri) y inicializa Spring Environment con fuentes de propiedades remotas.

El resultado neto de este comportamiento es que todas las aplicaciones cliente que quieran consumir el servidor de configuración necesitan un bootstrap.yml (o una variable de entorno) con la dirección del servidor configurada en spring.cloud.config.uri (el valor predeterminado es "http:/ /localhost:8888").

dependencia en pom

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

archivo application.yml

spring:
  cloud:
    bootstrap:
      enabled: true

archivo bootstrap.yml

spring:
  application:
    name: springcloudconfigdemoclient
  profiles:
    active: dev
  cloud:
    config:
      uri: http://root:s3cr3t@localhost:8888
    bootstrap:
      enabled: true

Primera búsqueda de descubrimiento

Si utiliza una implementación de DiscoveryClient, como Spring Cloud Netflix y Eureka Service Discovery o Spring Cloud Consul, puede hacer que el Config Server se registre con Discovery Service.

Si prefiere utilizar DiscoveryClient para ubicar el servidor de configuración, puede hacerlo configurando spring.cloud.config.discovery.enabled=true (el valor predeterminado es falso). Por ejemplo, con Spring Cloud Netflix, debe definir la dirección del servidor Eureka (por ejemplo, en eureka.client.serviceUrl.defaultZone). El precio por utilizar esta opción es un viaje extra de ida y vuelta a la red al inicio, para localizar el alta del servicio. El beneficio es que, siempre que el Servicio de descubrimiento sea un punto fijo, el Servidor de configuración puede cambiar sus coordenadas. El ID de servicio predeterminado es configserver, pero puede cambiarlo en el cliente configurando spring.cloud.config.discovery.serviceId (y en el servidor, de la forma habitual para un servicio, como configurando spring.application.name) .

Todas las implementaciones del cliente de descubrimiento admiten algún tipo de mapa de metadatos (por ejemplo, tenemos eureka.instance.metadataMap para Eureka). Es posible que sea necesario configurar algunas propiedades adicionales del servidor de configuración en sus metadatos de registro de servicio para que los clientes puedan conectarse correctamente. Si el servidor de configuración está protegido con HTTP básico, puede configurar las credenciales como usuario y contraseña. Además, si el servidor de configuración tiene una ruta de contexto, puede configurar configPath. Por ejemplo, el siguiente archivo YAML es para un servidor de configuración que es un cliente Eureka:

eureka:
  instance:
    ...
    metadataMap:
      user: osufhalskjrtl
      password: lviuhlszvaorhvlo5847
      configPath: /config

El cliente de configuración falla

En algunos casos, es posible que falle el inicio de un servicio si no puede conectarse al servidor de configuración. Si este es el comportamiento deseado, establezca la propiedad de configuración de arranque spring.cloud.config.fail-fast=true para que el cliente se detenga con una excepción.

Reintentar el cliente de configuración con spring.config.import

Reintentar funciona con la declaración Spring Boot spring.config.import y las propiedades normales funcionan. Sin embargo, si la declaración de importación está en un perfil, como application-prod.properties, necesitará una forma diferente de configurar el reintento. La configuración debe colocarse como parámetros de URL en la declaración de importación.

spring.config.import=configserver:http://root:s3cr3t@localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&multiplier=1.2&initial-interval=1100"

Vault

Cuando utiliza Vault como backend para su servidor de configuración, el cliente debe proporcionar un token para que el servidor recupere valores de Vault. Este token se puede proporcionar dentro del cliente configurando spring.cloud.config.token en bootstrap.yml, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
      token: YourVaultToken

Seguridad

Si utiliza seguridad HTTP básica en el servidor, los clientes necesitan saber la contraseña (y el nombre de usuario si no es el predeterminado). Puede especificar el nombre de usuario y la contraseña a través del URI del servidor de configuración o mediante propiedades separadas de nombre de usuario y contraseña, como se muestra en el siguiente ejemplo:

spring:
  cloud:
    config:
     uri: http://root:s3cr3t@localhost:8888

otra alternativa

spring:
  cloud:
    config:
     uri: http://localhost:8888
     username: user
     password: secret

Spring Cloud Gateway

Spring Gateway es un componente esencial en la arquitectura de microservicios, diseñado para proporcionar una capa de enrutamiento y filtrado en una aplicación basada en Spring. Actúa como un punto de entrada único para las solicitudes externas y permite dirigir el tráfico a servicios específicos en función de criterios predefinidos. Veamos en detalle cómo funciona Spring Gateway y los conceptos clave asociados a él.

En primer lugar, Spring Gateway actúa como un enrutador inverso, lo que significa que recibe todas las solicitudes de los clientes y las dirige a los servicios correspondientes basándose en reglas de enrutamiento configuradas. Esto permite una mayor flexibilidad y modularidad en la arquitectura de microservicios, ya que se pueden agregar, eliminar o actualizar servicios sin afectar directamente a los clientes.

Los componentes principales de Spring Cloud Gateway son:

- Route: es un componente importante en Spring Cloud Gateway. Consiste en el ID, URI de destino, predicados y filtros.

- Predicate: es lo mismo que el predicado de función de Java 8. Un Predicate simplemente evalúa la entrada frente a una condición definida por el Predicate y devuelve true si se cumple la condición. Aquí el predicate se utiliza para hacer coincidir las solicitudes HTTP. Un Route coincide si el predicate devuelve verdadero.

- Filter Chain: es una serie de filtros que se aplican a las solicitudes y respuestas entrantes. Se puede utilizar para diversos fines, como autenticación, solicitudes o transformación de respuestas, y muchos más.

https://media.geeksforgeeks.org/wp-content/uploads/20230515200450/Android-amia-client-API-gateway-microservice-01.webp

A continuación, vamos a explorar tres conceptos fundamentales en Spring Gateway: rutas (routes), predicados (predicates) y filtros (filters).

Routes

Rutas (Routes): Las rutas definen cómo se debe enrutar una solicitud entrante hacia un servicio específico. Cada ruta está asociada a un servicio de backend y puede incluir condiciones y transformaciones. Puedes configurar múltiples rutas en Spring Gateway para manejar diferentes tipos de solicitudes. A continuación, se muestra un ejemplo de configuración de ruta en el archivo application.yml para redirigir las solicitudes /api/users a un servicio de backend en https://jsonplaceholder.typicode.com/users:

spring:
  cloud:
    gateway:
      routes:
        - id: users_route
          uri: https://jsonplaceholder.typicode.com/users
          predicates:
            - Path=/api/users/**

En este ejemplo, hemos definido una ruta llamada users_route que redirige todas las solicitudes que coincidan con el patrón /api/users/** al servicio de backend en https://jsonplaceholder.typicode.com/users. El predicado Path se utiliza para evaluar si la solicitud coincide con la ruta especificada.

Configuración de Routes en Java:

@Configuration
public class GatewayConfig {

  @Bean
  public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("json_placeholder", r -> r.path("/api/users/**")
        .uri("https://jsonplaceholder.typicode.com/users"))
      .route("api_route", r -> r.path("/api/**")
        .filters(f -> f.stripPrefix(1))
        .uri("lb://api-service"))
      .build();
  }
}

En este ejemplo, se utiliza la clase RouteLocatorBuilder para crear un RouteLocator personalizado. Las rutas se definen utilizando el método route(). Puedes especificar un predicado utilizando el método path() y luego configurar el destino o servicio backend utilizando el método uri(). También puedes agregar filters utilizando el método filters().

Predicates

Predicados (Predicates): Los predicados son condiciones lógicas que se aplican a una solicitud entrante para determinar si debe ser enrutada a una ruta específica. Spring Gateway proporciona una variedad de predicados de fábrica listos para usar que se pueden combinar para crear reglas de enrutamiento más complejas. A continuación, se muestra un ejemplo de configuración de predicado en el archivo application.yml para enrutar las solicitudes solo si el encabezado X-Custom-Header está presente:

spring:
  cloud:
    gateway:
      routes:
        - id: custom_header_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Header=X-Custom-Header, \w+

En este ejemplo, hemos definido una ruta sin una ruta específica (uri está configurado a nivel de ruta) y hemos aplicado un predicado Header para verificar si el encabezado X-Custom-Header contiene un valor alfanumérico. Solo si el predicado se cumple, la solicitud se enruta a la ruta correspondiente.

Estos son ejemplos adicionales de los predicates disponibles en Spring Gateway.

  1. Predicate: Before

Este predicate permite enrutar solicitudes que ocurran antes de una fecha y hora específica. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que ocurran antes del 1 de enero de 2025 a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: before_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Before=2025-01-01T00:00:00+00:00
  1. Predicate: After

Este predicate permite enrutar solicitudes que ocurran después de una fecha y hora específica. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que ocurran después del 1 de enero de 2025 a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: after_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - After=2025-01-01T00:00:00+00:00
  1. Predicate: Between

Este predicate permite enrutar solicitudes que ocurran dentro de un rango de fechas y horas específico. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que ocurran entre el 1 de enero de 2025 y el 31 de diciembre de 2025 a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: between_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Between=2025-01-01T00:00:00+00:00, 2025-12-31T23:59:59+00:00
  1. Predicate: Method

Este predicate permite enrutar solicitudes basándose en el método HTTP utilizado, como GET, POST, PUT, DELETE, etc. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes GET a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: method_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Method=GET
  1. Predicate: Header

Este predicate permite enrutar solicitudes basándose en el contenido de un encabezado HTTP específico. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que contengan el encabezado Content-Type con el valor application/json:

spring:
  cloud:
    gateway:
      routes:
        - id: header_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Header=Content-Type, application/json
  1. Predicate: Path

Este predicate permite enrutar solicitudes basándose en la ruta de la URL. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que coincidan con el patrón /api/users/** a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: path_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Path=/api/users/**
  1. Predicate: Host

Este predicate permite enrutar solicitudes basándose en el nombre de host de la URL. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que se dirijan a example.com a una ruta específica:

spring:
  cloud:
    gateway:
      routes:
        - id: host_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Host=example.com
  1. Predicate: Query

Este predicate permite enrutar solicitudes basándose en los parámetros de consulta (query parameters) de la URL. A continuación, se muestra un ejemplo de configuración para enrutar solicitudes que contengan el parámetro de consulta category con el valor books:

spring:
  cloud:
    gateway:
      routes:
        - id: query_predicate_route
          uri: https://jsonplaceholder.typicode.com
          predicates:
            - Query=category, books

Estos ejemplos amplían la variedad de predicates disponibles en Spring Gateway.

Configuración de Predicates en Java:

@Configuration
public class GatewayConfig {

  @Bean
  public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("example_route", r -> r.path("/api/users")
        .and().method(HttpMethod.GET)
        .filters(f -> f.stripPrefix(1))
        .uri("https://jsonplaceholder.typicode.com"))
      .build();
  }
}

En este ejemplo, se utiliza el método and() para combinar múltiples predicados. En este caso, se utiliza el predicado path() para que la ruta coincida con /api/users y el predicado method() para que solo coincida con solicitudes GET.

Filters

Filtros (Filters): Los filtros permiten realizar transformaciones o aplicar lógica adicional a una solicitud o respuesta antes o después de que se enrute a un servicio backend. Spring Gateway ofrece una variedad de filtros de fábrica que se pueden utilizar para modificar o inspeccionar las solicitudes y respuestas. A continuación, se muestra un ejemplo de configuración de filtro en el archivo application.yml para agregar un encabezado personalizado a la solicitud antes de que se enrute al servicio backend:

spring:
  cloud:
    gateway:
      routes:
        - id: custom_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - AddRequestHeader=X-Custom-Header, Custom-Value

En este ejemplo, hemos definido una ruta sin una ruta específica y hemos aplicado un filtro AddRequestHeader para agregar el encabezado X-Custom-Header con el valor Custom-Value a la solicitud antes de que se envíe al servicio backend.

Estos son ejemplos adicionales de los filters disponibles en Spring Gateway:

  1. Filter: AddRequestHeader

Este filter permite agregar un encabezado personalizado a una solicitud antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para agregar el encabezado X-Custom-Header con el valor Custom-Value a la solicitud:

spring:
  cloud:
    gateway:
      routes:
        - id: add_request_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - AddRequestHeader=X-Custom-Header, Custom-Value
  1. Filter: AddRequestParameter

Este filter permite agregar un parámetro de solicitud personalizado a una solicitud antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para agregar el parámetro token con el valor abc123 a la solicitud:

spring:
  cloud:
    gateway:
      routes:
        - id: add_request_parameter_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - AddRequestParameter=token, abc123
  1. Filter: AddResponseHeader

Este filter permite agregar un encabezado personalizado a la respuesta del servicio backend antes de que se envíe al cliente. A continuación, se muestra un ejemplo de configuración para agregar el encabezado X-Custom-Header con el valor Custom-Value a la respuesta:

spring:
  cloud:
    gateway:
      routes:
        - id: add_response_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - AddResponseHeader=X-Custom-Header, Custom-Value
  1. Filter: MapRequestHeader

Este filter permite mapear un encabezado de solicitud existente a un nuevo valor antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para mapear el encabezado Authorization al encabezado X-Auth-Token:

spring:
  cloud:
    gateway:
      routes:
        - id: map_request_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - MapRequestHeader=Authorization, X-Auth-Token
  1. Filter: PrefixPath

Este filter permite agregar un prefijo a la ruta de la solicitud antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para agregar el prefijo /api a la ruta de la solicitud:

spring:
  cloud:
    gateway:
      routes:
        - id: prefix_path_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - PrefixPath=/api
  1. Filter: RedirectTo

Este filter permite redirigir una solicitud a una nueva ubicación o URL. A continuación, se muestra un ejemplo de configuración para redirigir la solicitud a https://example.com:

spring:
  cloud:
    gateway:
      routes:
        - id: redirect_to_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - RedirectTo=https://example.com
  1. Filter: RemoveRequestHeader

Este filter permite eliminar un encabezado de la solicitud antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para eliminar el encabezado X-Custom-Header de la solicitud:

spring:
  cloud:
    gateway:
      routes:
        - id: remove_request_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - RemoveRequestHeader=X-Custom-Header
  1. Filter: RemoveRequestParameter

Este filter permite eliminar un parámetro de la solicitud antes de que se enrute al servicio backend. A continuación, se muestra un ejemplo de configuración para eliminar el parámetro token de la solicitud:

spring:
  cloud:
    gateway:
      routes:
        - id: remove_request_parameter_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - RemoveRequestParameter=token
  1. Filter: RemoveResponseHeader

Este filter permite eliminar un encabezado de la respuesta del servicio backend antes de que se envíe al cliente. A continuación, se muestra un ejemplo de configuración para eliminar el encabezado X-Custom-Header de la respuesta:

spring:
  cloud:
    gateway:
      routes:
        - id: remove_response_header_filter_route
          uri: https://jsonplaceholder.typicode.com
          filters:
            - RemoveResponseHeader=X-Custom-Header

Estos ejemplos amplían la variedad de filters disponibles en Spring Gateway. Puedes combinar y personalizar estos filters según tus necesidades para manipular y transformar solicitudes y respuestas en tu microservicio gateway.

Implementación de Filters en Java:

@Component
public class CustomFilter implements GlobalFilter, Ordered {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // Lógica del filtro aquí
    return chain.filter(exchange);
  }

  @Override
  public int getOrder() {
    return Ordered.LOWEST_PRECEDENCE;
  }
}

En este ejemplo, se crea una clase de filtro personalizado que implementa la interfaz GlobalFilter y Ordered. Puedes implementar tu lógica de filtro dentro del método filter(). El método getOrder() especifica la prioridad del filtro.

Global Filter

Un filtro global en Spring Gateway es un filtro que se aplica a todas las rutas o rutas seleccionadas en tu configuración de gateway. Puedes utilizar los filtros globales para aplicar lógica común a todas las solicitudes que pasan a través del gateway, como autenticación, logging, manipulación de encabezados, etc.

Para agregar un filtro global en Spring Gateway, puedes seguir estos pasos:

  1. Crea una clase que implemente la interfaz GlobalFilter de Spring Cloud Gateway. Esta interfaz proporciona un método llamado filter() que se ejecuta para cada solicitud entrante.
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class CustomGlobalFilter implements GlobalFilter {
  
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // Lógica del filtro global
    return chain.filter(exchange);
  }
}
  1. Implementa la lógica del filtro dentro del método filter(). Puedes acceder a la solicitud entrante y la respuesta saliente a través del parámetro ServerWebExchange. Puedes realizar diversas operaciones, como modificar encabezados, autenticar la solicitud, agregar información de logging, etc.

  2. Utiliza la instancia GatewayFilterChain para continuar con el procesamiento de la solicitud. Llamar a chain.filter(exchange) permite que la solicitud continúe su flujo normal a través de los demás filtros y finalmente se enrute al servicio backend correspondiente.

  3. Registra la clase del filtro global en el contexto de Spring. Esto se puede hacer mediante la anotación @Component o utilizando las capacidades de configuración de Spring, como la clase @Configuration.

Una vez que hayas agregado el filtro global, se aplicará a todas las solicitudes que pasen a través del gateway. Puedes agregar múltiples filtros globales para implementar diferentes lógicas en tu aplicación.

Aquí hay un ejemplo adicional de un filtro global que agrega un encabezado personalizado a todas las solicitudes:

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Order(1)
public class CustomGlobalFilter implements GlobalFilter, Ordered {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    exchange.getRequest().mutate().headers(httpHeaders -> httpHeaders.add("X-Custom-Header", "Custom-Value"));
    return chain.filter(exchange);
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }
}

En este ejemplo, el filtro global implementa la interfaz Ordered y anota la clase con @Order(1) para establecer un orden específico de ejecución. El método getOrder() especifica la prioridad del filtro global. En este caso, se utiliza Ordered.HIGHEST_PRECEDENCE para que el filtro se ejecute antes que otros filtros.

Conclusión

Con el uso de Routes, Predicates y Filters se puede gestionar el enrutamiento de peticiones para redireccionarlos a diferentes servicios externos. Aquí un ejemplo de un archivo application.yml que incluye combinaciones de predicados y filtros:

spring:
  cloud:
    gateway:
      routes:
        - id: users_route
          uri: https://example.com/users
          predicates:
            - Path=/api/users/**
            - Method=GET
          filters:
            - RewritePath=/api/users/(?<segment>.*), /$\{segment}
        - id: clients_route
          uri: https://example3.com/api/clients
          predicates:
            - Path=/api/clients/**
            - Host=example3.com
            - Header=X-Custom-Header, SomeValue
          filters:
            - RewritePath=/api/clients/(?<segment>.*), /$\{segment}
            - AddRequestHeader=X-Another-Header, AnotherValue
        - id: reports_route
          uri: https://example3.com/reports
          predicates:
            - Path=/reports/**
            - Method=POST
          filters:
            - AddRequestHeader=X-Custom-Header, CustomValue
            - SetPath=/new-path

En este ejemplo, se agregaron predicados de combinación como Path, Method, Host y Header en las rutas. También se agregó un filtros adicionales como SetPath:

  1. La ruta users_route coincide con el predicado Path=/api/users/** y Method=GET. Redirige las solicitudes a https://example.com/users. El filtro RewritePath se utiliza para modificar la ruta de la solicitud.

  2. La ruta clients_route coincide con los predicados Path=/api/clients/**, Host=example3.com y Header=X-Custom-Header, SomeValue. Redirige las solicitudes a https://example3.com/api/clients. Los filtros RewritePath y AddRequestHeader se utilizan para modificar la ruta de la solicitud y agregar una cabecera adicional.

  3. La ruta reports_route coincide con los predicados Path=/reports/** y Method=POST. Redirige las solicitudes a https://example3.com/reports. El filtro AddRequestHeader se utiliza para agregar una cabecera personalizada a la solicitud. Además, se utiliza el filtro SetPath para establecer una nueva ruta en la solicitud.

Componentes Programación Reactiva en WebFlux

Spring Webflux es un módulo web reactivo, totalmente sin bloqueo, que admite contrapresión de flujos reactivos y se ejecuta en servidores como netty, Undertow y contenedores Servlet 3.1+.

La programación reactiva es un paradigma de la programación enfocado en el trabajo de datos finitos o infinitos de forma asíncrona.

Componentes

Dentro de la programación reactiva en Spring WebFlux es importante conocer el funcionamiento de cuatro componentes principales:

  • Publisher
  • Subscriber
  • Subscription
  • Processor

Publisher

Un Publisher es una interfaz que representa una fuente de datos reactiva. Es responsable de producir y emitir elementos a sus suscriptores. En Spring WebFlux, el tipo más común de Publisher es Flux, que emite una secuencia de elementos de manera asíncrona.

Los Publishers son la fuente de datos en la programación reactiva. Emiten secuencias de elementos que pueden ser consumidos por los Subscribers. Los Publishers pueden ser creados a partir de diversos tipos de fuentes de datos, como listas, bases de datos, servicios web, eventos de GUI, entre otros. Proporcionan los datos de manera asíncrona y bajo demanda, lo que evita bloqueos y permite una gestión eficiente de la memoria.

Ejemplo de un Publisher en Spring WebFlux utilizando Flux:

Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);

Subscriber

Un Subscriber es una interfaz que representa un consumidor de elementos emitidos por un Publisher. Es responsable de procesar los elementos recibidos, manejar errores y solicitar más elementos cuando sea necesario. En Spring WebFlux, los Subscriber se suscriben a un Publisher para recibir elementos.

Los Subscribers son los consumidores de los datos emitidos por los Publishers y procesados por los Processors. Los Subscribers implementan los métodos onSubscribe(), onNext(), onError() y onComplete() para recibir y manejar los datos emitidos. Pueden realizar cualquier tipo de acción con los datos, como imprimirlos en consola, almacenarlos en una base de datos, enviarlos a través de una red, entre otros. Además, los Subscribers pueden solicitar más elementos al Publisher a través de la Subscription, lo que permite un control flexible del flujo de datos.

Ejemplo de un Subscriber en Spring WebFlux utilizando Flux:

Subscriber<Integer> subscriber = new Subscriber<Integer>() {
    private Subscription subscription;

    @Override
    public void onSubscribe(Subscription subscription) {
        this.subscription = subscription;
        subscription.request(3); // Solicita los primeros 3 elementos
    }

    @Override
    public void onNext(Integer number) {
        System.out.println("Número recibido: " + number);
    }

    @Override
    public void onError(Throwable t) {
        System.err.println("Ocurrió un error: " + t.getMessage());
    }

    @Override
    public void onComplete() {
        System.out.println("Procesamiento completo");
    }
};

numbers.subscribe(subscriber);

Subscription

Una Subscription es una interfaz que representa la conexión entre un Publisher y un Subscriber. El Subscriber utiliza la Subscription para solicitar más elementos al Publisher o cancelar la subscripción. En Spring WebFlux, la suscripción se establece automáticamente al llamar al método subscribe() en un Publisher.

Las Subscriptions son los enlaces entre los Publishers y los Subscribers. Representan el contrato entre ambos y permiten establecer la comunicación y el flujo de datos. A través de las Subscriptions, los Subscribers pueden solicitar más elementos al Publisher, cancelar la suscripción en cualquier momento o realizar otras operaciones relacionadas con el flujo de datos.

Ejemplo de Subscription en Spring WebFlux utilizando Flux:

Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);
numbers.subscribe(
    number -> System.out.println("Número recibido: " + number),
    error -> System.err.println("Ocurrió un error: " + error.getMessage()),
    () -> System.out.println("Procesamiento completo"),
    subscription -> subscription.request(3) // Solicita los primeros 3 elementos
);

Processor

Un Processor es una interfaz que combina la funcionalidad de un Publisher y un Subscriber. Actúa como un intermediario entre el Publisher y el Subscriber, permitiendo transformar o filtrar los elementos antes de que sean consumidos por el Subscriber. En Spring WebFlux, el tipo más común de Processor es FluxProcessor.

Los Processors son componentes intermedios que reciben datos de los Publishers y los procesan antes de enviarlos a los Subscribers. Los Processors tienen la capacidad de aplicar transformaciones, filtrar elementos o realizar cualquier tipo de operación sobre los datos recibidos. También pueden actuar como un punto de control para el flujo de datos, permitiendo la suscripción de múltiples Subscribers y la propagación de los datos a través de múltiples hilos.

Ejemplo de un Processor en Spring WebFlux utilizando FluxProcessor:

FluxProcessor<Integer, Integer> processor = DirectProcessor.create();

processor
    .filter(number -> number % 2 == 0) // Filtra los números pares
    .map(number -> number * 2) // Duplica los números filtrados
    .subscribe(number -> System.out.println("Número procesado: " + number));

processor.onNext(1); // No se imprime, se filtra
processor.onNext(2); // Se imprime (2 * 2 = 4)
processor.onNext(3); // No se imprime, se filtra
processor.onNext(4); // Se imprime (4 * 2 = 8)
processor.onComplete();

En resumen, los componentes Publisher, Subscriber, Subscription y Processor son fundamentales en la programación reactiva con Spring WebFlux. El Publisher emite elementos, el Subscriber los consume, la Subscription gestiona la comunicación entre ellos y el Processor permite transformar o filtrar los elementos antes de que sean consumidos. Estos componentes son esenciales para construir aplicaciones reactivas y aprovechar los beneficios de la programación asíncrona y basada en eventos.

Ejemplos

Este es un ejemplo completo que muestra el funcionamiento de los componentes Publisher, Subscriber, Subscription y Processor en Spring WebFlux.

import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxProcessor;
import reactor.core.publisher.DirectProcessor;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

public class ReactorExample {
    public static void main(String[] args) {
        // Crear un Publisher (Flux) que emite una secuencia de números del 1 al 5
        Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);

        // Crear un Subscriber para procesar los números emitidos por el Publisher
        Subscriber<Integer> subscriber = new Subscriber<Integer>() {
            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription subscription) {
                this.subscription = subscription;
                subscription.request(3); // Solicitar los primeros 3 números
            }

            @Override
            public void onNext(Integer number) {
                System.out.println("Número recibido: " + number);
                if (number == 3) {
                    subscription.request(2); // Solicitar 2 números más
                }
            }

            @Override
            public void onError(Throwable t) {
                System.err.println("Ocurrió un error: " + t.getMessage());
            }

            @Override
            public void onComplete() {
                System.out.println("Procesamiento completo");
            }
        };

        // Suscribir el Subscriber al Publisher para comenzar a recibir los números
        numbers.subscribe(subscriber);

        // Crear un FluxProcessor para filtrar y transformar los números emitidos por el Publisher
        FluxProcessor<Integer, Integer> processor = DirectProcessor.create();

        processor
            .filter(number -> number % 2 == 0) // Filtrar los números pares
            .map(number -> number * 2) // Duplicar los números filtrados
            .subscribe(number -> System.out.println("Número procesado: " + number));

        processor.onNext(1); // No se imprime, se filtra
        processor.onNext(2); // Se imprime (2 * 2 = 4)
        processor.onNext(3); // No se imprime, se filtra
        processor.onNext(4); // Se imprime (4 * 2 = 8)
        processor.onComplete();
    }
}

En este ejemplo, el código muestra dos casos de uso diferentes. En la primera parte, se utiliza un Publisher (Flux) para emitir una secuencia de números. Un Subscriber se suscribe al Publisher y procesa los números recibidos. El Subscriber utiliza la Subscription para solicitar un número específico de elementos y controlar el flujo de datos.

En la segunda parte del ejemplo, se utiliza un FluxProcessor para filtrar y transformar los números emitidos por un Publisher. El Processor actúa como un intermediario entre el Publisher y el Subscriber, aplicando operaciones de filtrado y mapeo a los elementos antes de que sean consumidos por el Subscriber.

Este segundo ejemplo que muestra cómo un Processor actúa como intermediario entre un Publisher y un Subscriber, utilizando cadenas en lugar de números:

import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxProcessor;
import reactor.core.publisher.DirectProcessor;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

public class ReactorExample {
    public static void main(String[] args) {
        // Crear un Publisher (Flux) que emite una secuencia de cadenas
        Flux<String> strings = Flux.just("Hola", "Mundo", "Reactor", "es", "genial");

        // Crear un FluxProcessor para transformar las cadenas emitidas por el Publisher
        FluxProcessor<String, String> processor = DirectProcessor.create();

        // Aplicar una transformación a las cadenas emitidas por el Publisher
        Flux<String> transformed = processor.map(string -> string.toUpperCase());

        // Crear un Subscriber para procesar las cadenas transformadas
        Subscriber<String> subscriber = new Subscriber<String>() {
            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription subscription) {
                this.subscription = subscription;
                subscription.request(3); // Solicitar las primeras 3 cadenas
            }

            @Override
            public void onNext(String string) {
                System.out.println("Cadena recibida: " + string);
                if (string.length() > 4) {
                    subscription.request(2); // Solicitar 2 cadenas más
                }
            }

            @Override
            public void onError(Throwable t) {
                System.err.println("Ocurrió un error: " + t.getMessage());
            }

            @Override
            public void onComplete() {
                System.out.println("Procesamiento completo");
            }
        };

        // Suscribir el Subscriber al Flux transformado para comenzar a recibir las cadenas
        transformed.subscribe(subscriber);

        // Enviar las cadenas al Processor para que sean transformadas y emitidas al Subscriber
        strings.subscribe(processor);
    }
}

En este ejemplo, se crea un Publisher (strings) que emite una secuencia de cadenas. Luego, se crea un FluxProcessor (processor) que actúa como intermediario entre el Publisher y el Subscriber.

A continuación, se aplica una transformación a las cadenas emitidas por el Processor utilizando el método map(), en este caso, se convierten las cadenas a mayúsculas.

Luego, se crea un Subscriber que procesa las cadenas transformadas. El Subscriber solicita un número específico de cadenas utilizando la Subscription, y en este caso, solicita las primeras 3 cadenas y luego 2 cadenas más si la longitud de la cadena es mayor a 4.

Finalmente, se suscribe el Subscriber al Flux transformado para comenzar a recibir las cadenas transformadas. Y se envían las cadenas al Processor para que sean transformadas y emitidas al Subscriber.

En resumen, en este ejemplo, el Processor actúa como intermediario entre el Publisher y el Subscriber, permitiendo transformar las cadenas emitidas antes de que sean consumidas por el Subscriber. Esto demuestra el flujo de datos desde el Publisher al Processor y finalmente al Subscriber, mostrando cómo el Processor juega un papel central en la manipulación de los elementos emitidos.

Conclusiones

El uso de los cuatro elementos mencionados (Publishers, Processors, Subscribers y Subscriptions) es fundamental en la programación reactiva, ya que permiten implementar y gestionar flujos de datos asincrónicos de manera eficiente y flexible.

La importancia de estos elementos radica en que permiten implementar una arquitectura reactiva, donde se pueden manejar flujos de datos asincrónicos de manera eficiente, escalable y resiliente. La programación reactiva se basa en la idea de que los sistemas deben ser capaces de responder de forma rápida y eficiente a los cambios y eventos en el entorno, y estos elementos brindan las herramientas necesarias para lograrlo.

En conclusión, los Publishers, Processors, Subscribers y Subscriptions son elementos clave en la programación reactiva, ya que permiten implementar flujos de datos asincrónicos, gestionar la comunicación entre componentes y proporcionar un enfoque flexible y eficiente para el desarrollo de sistemas reactivos. Su uso adecuado y su comprensión son fundamentales para aprovechar al máximo los beneficios de la programación reactiva en el desarrollo de aplicaciones modernas.

WebFlux: Endpoints Funcionales

Spring Webflux es parte del Spring Framework más grande que se ocupa de la creación de aplicaciones reactivas. En un mundo donde los modelos de ejecución asincrónica y sin bloqueo se han vuelto fundamentales para manejar una gran cantidad de conexiones simultáneas

Los endpoints funcionales ofrecen un estilo más funcional para declarar rutas y manejar solicitudes, en contraste con el enfoque basado en anotaciones que se ve en Spring MVC. Este modelo enfatiza la inmutabilidad, proporcionando una forma concisa de definir la lógica de enrutamiento y manejo.

Ventajas de los endpoints funcionales

  • Simplicidad: los endpoints funcionales reducen el código repetitivo, lo que hace que las rutas y los controladores sean más sencillos de entender y mantener.
  • Modularidad: promueven un enfoque más modular, lo que permite una mejor organización del código y pruebas más sencillas.
  • Flexibilidad: los endpoints funcionales ofrecen una mayor flexibilidad en la forma en que se definen y manejan las rutas, lo que facilita la composición y reutilización de la lógica de ruta.

Implementación de endpoints funcionales

La implementación de endpoints funcionales en Spring Webflux implica el uso de interfaces RouterFunction y HandlerFunction. A continuación configuraremos un endpoint funcional básico.

Paso 1: Definir una función de controlador

La función del controlador se define en una clase anotada con @Component, lo que la convierte en un componente administrado por Spring

@Component
public class GreetingHandler {

    public Mono<ServerResponse> hello(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.TEXT_PLAIN)
                .body(BodyInserters.fromValue("Hello, Spring Webflux"));
    }
}

El método hello toma un objeto ServerRequest como argumento y devuelve un archivo Mono<ServerResponse>. El objeto ServerRequest representa la solicitud HTTP, mientras que Mono<ServerResponse> representa una respuesta asincrónica única o vacía.

  • Mono representa un flujo de cero o un solo elemento. Es similar a un Future en Java, pero con capacidades reactivas.
  • Flux representa un flujo de cero o más elementos.

Paso 2: Definir una función de enrutador

La función de enrutador es responsable de asignar las solicitudes HTTP a sus funciones de controlador correspondientes. Este mapeo se define en una clase anotada con @Configuration, lo que indica que proporciona información de configuración a Spring.

@Configuration
public class GreetingRouter {

    @Bean
    public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
        return RouterFunctions.route(RequestPredicates.GET("/hello")
                .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
                greetingHandler::hello);
    }
}

GreetingRouter contiene un método route anotado con @Bean, lo que significa que devuelve un bean administrado por Spring.

La asociación entre la ruta y su controlador es crucial para la funcionalidad de la aplicación, ya que dirige las solicitudes a la lógica adecuada para el procesamiento.

Juntas, estas dos partes crean un servicio web reactivo que escucha las solicitudes GET en la /hello ruta y responde con un saludo de texto sin formato. El uso de Mono para el tipo de respuesta en la función del controlador es una parte clave del enfoque reactivo, que permite operaciones de E/S asincrónicas y sin bloqueo dentro de Spring WebFlux.

Los endpoints funcionales también se pueden componer y anidar para escenarios de enrutamiento más complejos. Además, puede aplicar filtros para cuestiones transversales como registro, autenticación y métricas.

public RouterFunction<ServerResponse> nestedRoute(GreetingHandler greetingHandler) {
    return RouterFunctions
            .route(...).nest(path("/api"),
                builder -> builder.route(...).filter(...));
}

Conclusiones

Los endpoints funcionales de Spring Webflux proporcionan una forma poderosa y flexible de definir servicios web reactivos. Al aprovechar el modelo de programación funcional, los desarrolladores pueden lograr mayor claridad, modularidad y eficiencia en sus aplicaciones.

Spring Data R2DBC

Spring Data R2DBC es un proyecto de Spring que proporciona soporte para acceder a bases de datos relacionales de forma reactiva utilizando R2DBC (Reactive Relational Database Connectivity). R2DBC es una especificación que proporciona una API reactiva para interactuar con bases de datos relacionales en aplicaciones Java.

A diferencia de JDBC, que es síncrono y bloqueante, R2DBC permite operaciones asincrónicas y no bloqueantes, lo que lo hace ideal para aplicaciones reactivas.

Caracteristicas Principales

  • Reactividad: Spring Data R2DBC permite desarrollar aplicaciones reactivas que pueden manejar un gran número de solicitudes concurrentes de manera eficiente
  • Integración con Spring: Al ser parte del ecosistema de Spring, Spring Data R2DBC se integra de manera transparente con otros proyectos de Spring, como Spring Boot y Spring Framework
  • Soporte para múltiples bases de datos: Spring Data R2DBC ofrece soporte para varias bases de datos relacionales, incluidas PostgreSQL, MySQL, Microsoft SQL Server, H2.
  • Programación declarativa: Al igual que otros proyectos de Spring Data, Spring Data R2DBC permite a los desarrolladores interactuar con la base de datos de manera declarativa, lo que significa que pueden definir consultas y operaciones utilizando interfaces y métodos de repositorio sin tener que escribir código SQL manualmente.
  • Soporte transaccional: Spring Data R2DBC integra el soporte transaccional de Spring, lo que permite a los desarrolladores administrar transacciones de manera eficiente y garantizar la consistencia de los datos en aplicaciones reactivas.

Dependencia

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-r2dbc</artifactId>
    </dependency>

Antes debemos habilitar el uso repositorios r2dbc

package com.mcalvaro.webfluxfunctionalendpoints;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;

@SpringBootApplication
@EnableR2dbcRepositories
public class WebfluxFunctionalEndpointsApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxFunctionalEndpointsApplication.class, args);
    }

}

Respositorios R2DBC

Spring Data R2DBC nos permite crear repositorios creando interfaces extendiendo la interfaz ReactiveCrudRepository También se puede extender de la interfaz ReactiveSortingRepository, una extensión de ReactiveCrudRepository, que proporciona métodos adicionales para ordenar entidades.

package com.mcalvaro.webfluxfunctionalendpoints.repository;

import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;

import com.mcalvaro.webfluxfunctionalendpoints.entity.Product;

@Repository
public interface ProductRepository extends ReactiveCrudRepository<Product, Long> {

}

Manejo de Transacciones

ReactiveTransactionManageres una abstracción de gestión de transacciones para integraciones reactivas y sin bloqueo que utiliza recursos transaccionales. Es una base para los métodos reactivos @Transactional que devuelven tipos Publisher y para la gestión programática de transacciones que utiliza TransactionalOperator.

    @Transactional
    public Mono<ServerResponse> create(ServerRequest request) {

        Mono<Product> productMono = request.bodyToMono(Product.class);

        return productMono.flatMap(product -> productRepository.save(product))
                .flatMap(savedProduct -> ServerResponse.status(HttpStatus.CREATED).bodyValue(savedProduct));
    }

Security con WebFlux

Introducción

Spring Security es un poderoso y altamente customizable framework de control de acceso.

Para alinearse con la característica reactiva introducida en Spring 5, Spring Security 5 añadió un nuevo módulo llamado spring-secuirty-webflux.

Image description

Se recibe una solicitud del usuario. Se redirige al puerto del Gateway desplegado, se sustituye por el nombre del microservicio y, a continuación, van los puntos finales habituales del microservicio especificado. Por ejemplo: localhost:8888/microserviceName/users.

Configurar Spring Security

Cuando se desarrolla una aplicación Spring Web, añadir lo siguiente a las dependencias de su proyecto.

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-webflux</artifactId>
</dependency>

Cree una clase de configuración, añada la anotación @EnableWebFluxSecurity para habilitar el soporte de WebFlux para la seguridad de Spring.

@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	SecurityWebFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
		return http
			.authorizeExchange()
				.pathMatchers(HttpMethod.GET, "/posts/**").permitAll()
                .pathMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN")
				//.pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
				.anyExchange().authenticated()
				.and()
			.build();
	}

	private Mono<AuthorizationDecision> currentUserMatchesPath(Mono<Authentication> authentication, AuthorizationContext context) {
		return authentication
			.map( a -> context.getVariables().get("user").equals(a.getName()))
			.map( granted -> new AuthorizationDecision(granted));
	}

	@Bean
	public MapUserDetailsRepository userDetailsRepository() {
		UserDetails rob = User.withUsername("test").password("test123").roles("USER").build();
		UserDetails admin = User.withUsername("admin").password("admin123").roles("USER","ADMIN").build();
		return new MapUserDetailsRepository(rob, admin);
	}

}
  1. Utilice la anotación @EnableWebFluxSecurity para habilitar la seguridad de la aplicación basada en spring-webflux.
  2. El bean SecurityWebFilterChain es imprescindible para configurar los detalles de Spring Security. El bean HttpSecurity es de spring-secuirty-webflux, similar con la versión general, pero maneja WebExhange en lugar de WebRequest basado en Servlets.
  3. Se introduce una nueva interfaz UserDetailsRepository que está alineada con las APIs de Reactor. Por defecto, se proporciona una implementación basada en Map en memoria MapUserDetailsRepository, se la puede personalizar implementando la interfaz UserDetailsRepository.

Arranca la aplicación y comprueba que la configuración de Spring Security funciona como se espera.

mvn spring-boot:run

Una vez iniciado, intente añadir un nuevo mensaje sin autenticación:

#curl -v  -X POST http://localhost:8080/posts -H "Content-Type:application/json" -d "{\"title\":\"My Post\",\"content\":\"content of My Post\"}"
Note: Unnecessary use of -X or --request, POST is already inferred.
* timeout on name lookup is not supported
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /posts HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.1
> Accept: */*
> Content-Type:application/json
> Content-Length: 42
>
* upload completely sent off: 42 out of 42 bytes
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Basic realm="Realm"
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< content-length: 0
<
* Connection #0 to host localhost left intact

El servidor rechaza la solicitud del cliente y devuelve un error 401 (401 Unauthorized).

Utilice las credenciales predefinidas user:password para autenticarse y vuelva a enviar la solicitud de publicación.

curl -v  -X POST http://localhost:8080/posts -u "user:password" -H "Content-Type:application/json" -d "{\"title\":\"My Post\",\"content\":\"content of My Post\"}"
Note: Unnecessary use of -X or --request, POST is already inferred.
* timeout on name lookup is not supported
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'test'
> POST /posts HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dGVzdDp0ZXN0MTIz
> User-Agent: curl/7.54.1
> Accept: */*
> Content-Type:application/json
> Content-Length: 50
>
* upload completely sent off: 50 out of 50 bytes
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< set-cookie: SESSION=b99124f7-c0a0-4507-b9be-34718af3d137; HTTPOnly
<
{"id":"59906f9d3c44060e044fb378","title":"My Post","content":"content of My Post","createdDate":[2017,8,13,23,26,21,392000000]}* Connection #0 to host localhost left intact

Se realiza con éxito, y devuelve el nuevo post creado.

Configurar Spring Security en aplicaciones Spring Boot

Para aplicaciones Spring Boot, añada en las dependencias del proyecto junto con spring-boot-starter-security.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

A continuación, añada una clase @Configuration para personalizar sus reglas de seguridad.

@Configration
class SecurityConfig {

	@Bean
	SecurityWebFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
		return http
			.authorizeExchange()
				.pathMatchers(HttpMethod.GET, "/posts/**").permitAll()
                .pathMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN")
				//.pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
				.anyExchange().authenticated()
				.and()
			.build();
	}
// ...
}

Restricciones a nivel de método

Como en las aplicaciones WebMvc tradicionales de Spring, puedes utilizar una anotación @PreAuthorize("hasRole('ADMIN')") en tus métodos para evitar la ejecución de este método si la evaluación de la expresión definida en el PreAuthorize es falsa.

Para habilitar la seguridad a nivel de método, añade un extra @EnableReactiveMethodSecurity a tu clase de configuración.

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig {
}

En sus códigos de negocio, añada la anotación @PreAuthorize("hasRole('ADMIN')") a su método.

@PreAuthorize("hasRole('ADMIN')")
Mono<Post> delete(Long id) {
	Post deleted = data.get(id);
	data.remove(id);
	return Mono.just(deleted);
}

Si quieres anotaciones compatibles con Java EE/Jakarta EE, como RolesAllowed , etc. Añade un atributo a la anotación @EnableGlobalMethodSecurity.

@EnableGlobalMethodSecurity(jsr250Enabled = true)

Personalización de UserDetailsRepository

Cargar usuarios desde un archivo de propiedades

Spring Security proporciona un UserDetailsRepositoryResourceFactoryBean que permite cargar usuarios desde un fichero de propiedades para crear el UserDetailsRepository de sus aplicaciones.

@Bean
public UserDetailsRepositoryResourceFactoryBean userDetailsService() {
	return UserDetailsRepositoryResourceFactoryBean
		.fromResourceLocation("classpath:users.properties");
}

El contenido de users.properties es similar al siguiente.

user=password,ROLE_USER
admin=password,ROLE_USER,ROLE_ADMIN

La clave es el nombre de usuario, el valor es password, y sus roles.

Autenticación basada en formularios

La autenticación basada en formularios es la forma más sencilla de proteger las páginas web.

Por defecto Spring security detecta el tipo de contenido de la petición y decide si debe ser autenticado por un formulario de login.

Por ejemplo, hay una clase HomeController.

@Controller
@Slf4j
public class HomeController {
    private final PostRepository posts;

    HomeController(PostRepository posts) {
        this.posts = posts;
    }

    @GetMapping("/")
    public String home(final Model model) {

        Flux<Post> postsAll = this.posts.findAll();
        model.addAttribute("posts", postsAll);
        return "home";
    }
}

Y el contenido del archivo de plantilla home.

<!-- /WEB-INF/templates/home.ftl -->
<!DOCTYPE html>
<html>
    <head>
        <title>Simple Blog Posts</title>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    </head>
    <body>
        <h1>All posts</h1>
        <div>
            <table>
                <thead>
                    <tr>
                        <th> ID</th>
                        <th>Title </th>
                        <th>Content</th>
                    </tr>
                </thead>
                <tbody>

                        <#list posts as post>        
                        <tr>
                            <td>${post.id}</td>
                            <td>${post.title}</td>
                            <td>${post.content}</td>
                        </tr>
                        <#else>
                        nothing
                        </#list>

                </tbody>
            </table>  

        </div>
    </body>
</html>

Añade una configuración sencilla para proteger la página de inicio.

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
        return http.authorizeExchange()
                    .anyExchange().authenticated()
//                .and()
//                    .formLogin()
                .and()
                .build();
    }
    //...
}

Por defecto, cuando se accede a la URL de inicio por defecto http://localhost:8080/, el HomeController intentará manejar la petición y renderizar la plantilla home.flt en una página HTML, Spring Security invocará la autenticación basada en form para proteger las páginas web.

Por defecto, Spring Security proporciona un sencillo formulario de inicio de sesión. Alternativamente, puede personalizar el atributo del formulario de inicio de sesión o especificar una nueva página de inicio de sesión en la configuración de seguridad de Spring.

.and().formLogin()...
⚠️ **GitHub.com Fallback** ⚠️