Spring wiki - emizoripx/java-wiki GitHub Wiki
- Spring Estereotipos
- Spring Handler Exceptions
- Spring Cloud OpenFeign
- Spring Cloud Config
- Spring Cloud Gateway
- Componentes Programación Reactiva en WebFlux
- WebFlux: Endpoints Funcionales
- Spring Data R2DBC
- Security con webflux
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.
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.
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.
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 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.
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.
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.
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);
}
}- 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.
<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>- A continuación, debemos agregar
@EnableFeignClientsa 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());
}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
-
DecoderResponseEntityDecoder , que envuelve SpringDecoder , utilizado para decodificar la respuesta -
EncoderSpringEncoder se utiliza para codificar RequestBody. -
LoggerSlf4jLogger es el registrador predeterminado utilizado por Feign. -
ContractSpringMvcContract , que proporciona procesamiento de anotaciones. -
Feign.BuilderHystrixFeign.Builder se utiliza para construir los componentes. -
ClientLoadBalancerFeignClient o cliente Feign predeterminado.
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.LevelRetryerErrorDecoderRequest.OptionsCollection<RequestInterceptor>SetterFactoryQueryMapEncoderCapability
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");
};
}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.
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();
}
}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:
-
NONEsin registro, que es el valor predeterminado -
BASICregistra solo el método de solicitud, la URL y el estado de la respuesta -
HEADERSregistra la información básica junto con los encabezados de solicitud y respuesta. -
FULLregistre el cuerpo, los encabezados y los metadatos tanto para la solicitud como para la respuesta
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 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.
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-repoDependencias 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.)
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:8888Dependencias 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 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-repodonde ${user.home}/config-repo es un repositorio git que contiene archivos YAML y de propiedades.
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).
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.
¿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).
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 ('').
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: truePuede 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: 4Spring 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}
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/localSi {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).
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/stagingSpring 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.gitEn 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.
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.
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: strongpasswordSpring 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}
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-----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: truePuede 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.
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.
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"
}- File Based Repositories
- Vault Server
- CredHub Server
- AWS Secrets Manager
- AWS Parameter Store
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.
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
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"}
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.0Cuando utiliza AWS Parameter Store como backend, puede compartir la configuración con todas las aplicaciones colocando propiedades dentro de la jerarquía /application.
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=truebootstrap.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: 1Tabla
create table PROPERTIES (
id serial primary key,
CREATED_ON timestamp ,
APPLICATION text,
PROFILE text,
LABEL text,
PROP_KEY text,
VALUE text
);
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: 16379Spring 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: bucket1Spring 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: 5Spring 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: _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/repoSi 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: 1El 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: barLos ejemplos anteriores hacen que todas las aplicaciones que son clientes de configuración lean foo=bar, independientemente de su propia configuración.
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: developmentPuede 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).
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).
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}FKSAJDFGYOS8F7GLHAKERGFHLSAJPuede 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
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).
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. |
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: changemeA 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.
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).
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.
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.
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: truearchivo bootstrap.yml
spring:
application:
name: springcloudconfigdemoclient
profiles:
active: dev
cloud:
config:
uri: http://root:s3cr3t@localhost:8888
bootstrap:
enabled: trueSi 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: /configEn 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 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"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: YourVaultTokenSi 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:8888otra alternativa
spring:
cloud:
config:
uri: http://localhost:8888
username: user
password: secretSpring 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.
A continuación, vamos a explorar tres conceptos fundamentales en Spring Gateway: rutas (routes), predicados (predicates) y filtros (filters).
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.
@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().
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.
- 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- 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- 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- 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- 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- 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/**- 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- 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, booksEstos ejemplos amplían la variedad de predicates disponibles en Spring Gateway.
@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.
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-ValueEn 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:
- 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- 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- 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- 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- 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- 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- 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- 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- 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-HeaderEstos 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.
@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.
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:
- Crea una clase que implemente la interfaz
GlobalFilterde Spring Cloud Gateway. Esta interfaz proporciona un método llamadofilter()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);
}
}-
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ámetroServerWebExchange. Puedes realizar diversas operaciones, como modificar encabezados, autenticar la solicitud, agregar información de logging, etc. -
Utiliza la instancia
GatewayFilterChainpara continuar con el procesamiento de la solicitud. Llamar achain.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. -
Registra la clase del filtro global en el contexto de Spring. Esto se puede hacer mediante la anotación
@Componento 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.
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-pathEn 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:
-
La ruta
users_routecoincide con el predicadoPath=/api/users/**yMethod=GET. Redirige las solicitudes ahttps://example.com/users. El filtroRewritePathse utiliza para modificar la ruta de la solicitud. -
La ruta
clients_routecoincide con los predicadosPath=/api/clients/**,Host=example3.comyHeader=X-Custom-Header, SomeValue. Redirige las solicitudes ahttps://example3.com/api/clients. Los filtrosRewritePathyAddRequestHeaderse utilizan para modificar la ruta de la solicitud y agregar una cabecera adicional. -
La ruta
reports_routecoincide con los predicadosPath=/reports/**yMethod=POST. Redirige las solicitudes ahttps://example3.com/reports. El filtroAddRequestHeaderse utiliza para agregar una cabecera personalizada a la solicitud. Además, se utiliza el filtroSetPathpara establecer una nueva ruta en la solicitud.
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.
Dentro de la programación reactiva en Spring WebFlux es importante conocer el funcionamiento de cuatro componentes principales:
- Publisher
- Subscriber
- Subscription
- Processor
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);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);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
);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.
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.
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.
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.
- 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.
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.
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.
-
Monorepresenta un flujo de cero o un solo elemento. Es similar a un Future en Java, pero con capacidades reactivas. -
Fluxrepresenta un flujo de cero o más elementos.
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(...));
}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 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.
- 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.
<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);
}
}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> {
}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));
}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.
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.
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);
}
}- Utilice la anotación
@EnableWebFluxSecuritypara habilitar la seguridad de la aplicación basada enspring-webflux. - El bean
SecurityWebFilterChaines imprescindible para configurar los detalles de Spring Security. El beanHttpSecurityes despring-secuirty-webflux, similar con la versión general, pero manejaWebExhangeen lugar deWebRequestbasado en Servlets. - Se introduce una nueva interfaz
UserDetailsRepositoryque está alineada con las APIs de Reactor. Por defecto, se proporciona una implementación basada enMapen memoriaMapUserDetailsRepository, se la puede personalizar implementando la interfazUserDetailsRepository.
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.
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();
}
// ...
}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)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.
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()...
