Configura automáticamente y ejecuta el servidor gRPC integrado con beans habilitados para @GRpcService como parte de la aplicación Spring-boot (video corto)
Se recomienda a los usuarios Gradle
que apliquen el complemento:
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
El complemento gradle io.github.lognet.grpc-spring-boot simplifica drásticamente la configuración del proyecto.
repositories {
mavenCentral()
// maven { url "https://oss.sonatype.org/content/repositories/snapshots" } // for snapshot builds
}
dependencies {
implementation ' io.github.lognet:grpc-spring-boot-starter:5.1.5 '
}
De forma predeterminada, el iniciador extrae io.grpc:grpc-netty-shaded
como dependencia transitiva, si se ve obligado a utilizar una dependencia grpc-netty
pura:
implementation ( ' io.github.lognet:grpc-spring-boot-starter:5.1.5 ' ) {
exclude group : ' io.grpc ' , module : ' grpc-netty-shaded '
}
implementation ' io.grpc:grpc-netty:1.58.0 ' // (1)
Asegúrese de obtener la versión que coincida con la versión.
La presencia de ambas bibliotecas en classpath también es compatible con la propiedad grpc.netty-server.on-collision-prefer-shaded-netty
.
Si está utilizando el complemento Spring Boot Dependency Management, es posible que no obtenga la misma versión con la que se compiló la versión iniciada, lo que causa un problema de incompatibilidad binaria.
En este caso, deberá configurar de forma forzada y explícita la versión grpc
que se utilizará (consulte la matriz de versiones aquí):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
Las notas de la versión con la matriz de compatibilidad se pueden encontrar aquí |
Siga esta guía para generar interfaces stub y de servidor a partir de sus archivos .proto
.
Si trabaja con Maven, utilice este enlace.
Anote las implementaciones de la interfaz de su servidor con @org.lognet.springboot.grpc.GRpcService
Opcionalmente, configure el puerto del servidor en su application.yml/properties
. El puerto predeterminado es 6565
.
grpc :
port : 6565
Se puede definir un puerto aleatorio estableciendo el puerto en 0 .El puerto real que se está utilizando se puede recuperar usando la anotación @LocalRunningGrpcPort en el campo int que inyectará el puerto en ejecución (configurado explícitamente o seleccionado al azar). |
Opcionalmente, habilite la reflexión del servidor (consulte https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md)
grpc :
enableReflection : true
Opcionalmente, establezca el orden de las fases de inicio (el valor predeterminado es Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
Opcionalmente, establezca la cantidad de segundos que se esperarán para que finalicen las llamadas preexistentes durante el cierre ordenado del servidor. Durante este tiempo se rechazarán nuevas llamadas. Un valor negativo equivale a un período de gracia infinito. El valor predeterminado es 0
(significa que no espere).
grpc :
shutdownGrace : 30
Las propiedades del servidor específicas de Netty se pueden especificar en el prefijo grpc.netty-server
.
Al configurar uno de los valores de grpc.netty-server.xxxx
implícitamente está configurando el transporte para que esté basado en Netty.
grpc :
netty-server :
keep-alive-time : 30s (1)
max-inbound-message-size : 10MB (2)
primary-listen-address : 10.10.15.23:0 (3)
additional-listen-addresses :
- 192.168.0.100:6767 (4)
on-collision-prefer-shaded-netty : false (5)
Las propiedades del tipo Duration
se pueden configurar con el formato de valor de cadena que se describe aquí.
Las propiedades del tipo DataSize
se pueden configurar con el valor de cadena descrito aquí
Expuesto en IP de red externa con puerto personalizado.
Formato del valor de cadena de propiedades del tipo SocketAddress
:
host:port
(si el valor port
es menor que 1, utiliza un valor aleatorio)
host:
(utiliza el puerto grpc predeterminado, 6565
)
Expuesto en la IP de la red interna también con el puerto predefinido 6767
.
En caso de que tenga bibliotecas netty pure
y shaded
en las dependencias, elija el tipo de NettyServerBuilder
que debe crearse. Este es el tipo que se pasará a GRpcServerBuilderConfigurer
(consulte Configuración personalizada del servidor gRPC), el valor predeterminado es true
(es decir, io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
si es false
).
El iniciador también admite el in-process server
, que debe usarse con fines de prueba:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
Deshabilita el servidor predeterminado ( NettyServer
).
Habilita el servidor in-process
.
Si habilita tanto NettyServer como el servidor in-process , ambos compartirán la misma instancia de HealthStatusManager y GRpcServerBuilderConfigurer (consulte Configuración personalizada del servidor gRPC). |
En el proyecto grpc-spring-boot-starter-demo
puedes encontrar ejemplos completamente funcionales con pruebas de integración.
La definición de servicio del archivo .proto
se ve así:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
Tenga en cuenta la clase io.grpc.examples.GreeterGrpc.GreeterImplBase
generada que extiende io.grpc.BindableService
.
Todo lo que necesita hacer es anotar la implementación de su servicio con @org.lognet.springboot.grpc.GRpcService
@ GRpcService
public static class GreeterService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
final GreeterOuterClass . HelloReply . Builder replyBuilder = GreeterOuterClass . HelloReply . newBuilder (). setMessage ( "Hello " + request . getName ());
responseObserver . onNext ( replyBuilder . build ());
responseObserver . onCompleted ();
}
}
El iniciador admite el registro de dos tipos de interceptores: global y por servicio .
En ambos casos, el interceptor debe implementar la interfaz io.grpc.ServerInterceptor
.
Por servicio
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Se creará una instancia LogInterceptor
a través de Spring Factory si hay un bean de tipo LogInterceptor
, o mediante un constructor sin argumentos en caso contrario.
Global
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
También se admite la anotación en el método de fábrica de configuración de Java:
@ Configuration
public class MyConfig {
@ Bean
@ GRpcGlobalInterceptor
public ServerInterceptor globalInterceptor (){
return new ServerInterceptor (){
@ Override
public < ReqT , RespT > ServerCall . Listener < ReqT > interceptCall ( ServerCall < ReqT , RespT > call , Metadata headers , ServerCallHandler < ReqT , RespT > next ) {
// your logic here
return next . startCall ( call , headers );
}
};
}
}
El servicio particular también tiene la oportunidad de desactivar los interceptores globales:
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Los interceptores globales se pueden ordenar utilizando las anotaciones @Ordered
o @Priority
de Spring. Siguiendo la semántica de ordenamiento de Spring, los valores de orden inferior tienen mayor prioridad y se ejecutarán primero en la cadena del interceptor.
@ GRpcGlobalInterceptor
@ Order ( 10 )
public class A implements ServerInterceptor {
// will be called before B
}
@ GRpcGlobalInterceptor
@ Order ( 20 )
public class B implements ServerInterceptor {
// will be called after A
}
El iniciador utiliza interceptores integrados para implementar el manejo de errores, Spring Security
, Validation
e integración Metrics
. Su orden también puede controlarse mediante las siguientes propiedades:
grpc.recovery.interceptor-order
(error al manejar el orden del interceptor, el valor predeterminado es Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
(el valor predeterminado es Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
(el valor predeterminado es Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
(el valor predeterminado es Ordered.HIGHEST_PRECEDENCE+20
)
Esto le brinda la posibilidad de configurar el orden deseado de los interceptores integrados y personalizados.
Sigue leyendo!!! hay mas
La forma en que funciona el interceptor grpc es que intercepta la llamada y devuelve el escucha de llamadas del servidor, que a su vez también puede interceptar el mensaje de solicitud, antes de reenviarlo al controlador de llamadas de servicio real:
Al establecer la propiedad grpc.security.auth.fail-fast
en false
todos los interceptores descendentes, así como todos los interceptores ascendentes (On_Message), aún se ejecutarán en caso de falla de autenticación/autorización.
Suponiendo que interceptor_2
es securityInterceptor
:
Para autenticación/autorización fallida con grpc.security.auth.fail-fast=true
(predeterminado):
Para autenticación/autorización fallida con grpc.security.auth.fail-fast=false
:
Este inicio cuenta con el respaldo nativo del proyecto spring-cloud-sleuth
.
Continúe investigando la integración de grpc.
Al incluir la dependencia org.springframework.boot:spring-boot-starter-actuator
, el iniciador recopilará métricas del servidor gRPC, desglosadas por
method
: método de servicio gRPC FQN (nombre completo)
result
: código de estado de respuesta
address
: dirección local del servidor (si expuso direcciones de escucha adicionales, con la propiedad grpc.netty-server.additional-listen-addresses
)
Después de configurar el exportador de su elección, debería ver el timer
llamado grpc.server.calls
.
Al definir el bean GRpcMetricsTagsContributor
en el contexto de su aplicación, puede agregar etiquetas personalizadas al temporizador grpc.server.calls
.
También puede utilizar el bean RequestAwareGRpcMetricsTagsContributor
para etiquetar llamadas unarias y de transmisión .
La demostración está aquí
Mantenga baja la dispersión para no hacer estallar la cardinalidad de la métrica. |
RequestAwareGRpcMetricsTagsContributor
aún se puede ejecutar para una autenticación fallida si el interceptor metric
tiene mayor prioridad que el interceptor security
y grpc.security.auth.fail-fast
está configurado en false
.
Este caso está cubierto por esta prueba.
Asegúrate de leer el capítulo sobre pedidos de interceptores. |
Asegúrese de incluir las siguientes dependencias:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
Configuración:
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
Los puntos finales estándar /actuator/metrics
y /actuator/prometheus
representarán las métricas grpc.server.calls
(consulte la demostración aquí).
Propuesta de desguace de GRPC |
El iniciador se puede configurar automáticamente para validar mensajes de servicio gRPC de solicitud/respuesta. Continúe con Implementación de la validación de mensajes para obtener detalles de configuración.
El iniciador define internamente el bean de tipo java.util.function.Consumer
que se considera para el registro de funciones cuando spring-cloud-stream
está en classpath, lo cual no es deseable ( spring-cloud-stream
registra automáticamente el canal si tiene exactamente un bean Consumidor/Proveedor/Función en el contexto de la aplicación, por lo que ya tiene uno si usa este iniciador junto con spring-cloud-stream
).
De acuerdo con esto, se recomienda utilizar la propiedad spring.cloud.function.definition
en aplicaciones listas para producción y no confiar en el descubrimiento automático.
Consulte la demostración de GRPC Kafka Stream, la parte esencial es esta línea.
El iniciador proporciona soporte integrado para autenticar y autorizar usuarios aprovechando la integración con el marco Spring Security.
Consulte las secciones sobre Integración de Spring Security para obtener detalles sobre los proveedores de autenticación admitidos y las opciones de configuración.
La seguridad del transporte se puede configurar utilizando el certificado raíz junto con su ruta de clave privada:
grpc :
security :
cert-chain : classpath:cert/server-cert.pem
private-key : file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
El valor de ambas propiedades está en el formato admitido por ResourceEditor.
El lado del cliente debe configurarse en consecuencia:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
Este iniciador extraerá la dependencia io.netty:netty-tcnative-boringssl-static
de forma predeterminada para admitir SSL.
Si necesita otra compatibilidad con SSL/TLS, excluya esta dependencia y siga la Guía de seguridad.
Si se necesita un ajuste más detallado para la configuración de seguridad, utilice el configurador personalizado descrito en Configuración personalizada del servidor gRPC. |
Para interceptar la instancia io.grpc.ServerBuilder
utilizada para construir io.grpc.Server
, puede agregar un bean que hereda de org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
a su contexto y anular el método configure
.
También se admiten varios configuradores.
En el momento de la invocación del método configure
, todos los servicios descubiertos, incluidos sus interceptores, se habían agregado al constructor pasado.
En su implementación del método configure
, puede agregar su configuración personalizada:
@ Component
public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer {
@ Override
public void configure ( ServerBuilder <?> serverBuilder ){
serverBuilder
. executor ( YOUR EXECUTOR INSTANCE )
. useTransportSecurity ( YOUR TRANSPORT SECURITY SETTINGS );
(( NettyServerBuilder ) serverBuilder ) // cast to NettyServerBuilder (which is the default server) for further customization
. sslContext ( GrpcSslContexts // security fine tuning
. forServer (...)
. trustManager (...)
. build ())
. maxConnectionAge (...)
. maxConnectionAgeGrace (...);
}
};
}
@ Component
public class MyCustomCompressionGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer {
@ Override
public void configure ( ServerBuilder <?> serverBuilder ){
serverBuilder
. compressorRegistry ( YOUR COMPRESSION REGISTRY )
. decompressorRegistry ( YOUR DECOMPRESSION REGISTRY ) ;
}
};
}
Si habilita tanto NettyServer como los servidores in-process , el método configure se invocará en la misma instancia del configurador.Si necesita diferenciar entre los serverBuilder pasados, puede verificar el tipo.Esta es la limitación actual. |
GRpcServerInitializedEvent
se publica al iniciar el servidor, puede consumirlo utilizando la API de Spring normal.
A partir de la versión 5.1.0
, spring-boot-starter-gradle-plugin integra el complemento de protocolo reactivo-grpc de SalesForce:
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
Aquí están las pruebas y el servicio de muestra de grpc reactivo.
El iniciador registra el GRpcExceptionHandlerInterceptor
, que es responsable de propagar la excepción lanzada por el servicio a los controladores de errores.
El método de manejo de errores podría registrarse teniendo un bean anotado @GRpcServiceAdvice
con métodos anotados con anotaciones @GRpcExceptionHandler
.
Estos se consideran controladores de errores global
y se invoca el método con el parámetro de tipo de excepción más cercano en la jerarquía de tipos a la excepción lanzada.
La firma del controlador de errores debe seguir el siguiente patrón:
Tipo de devolución | Parámetro 1 | Parámetro 2 |
---|---|---|
io.grpc.Estado | cualquier tipo de | Ámbito de excepción GRpc |
@ GRpcServiceAdvice
class MyHandler1 {
@ GRpcExceptionHandler
public Status handle ( MyCustomExcpetion exc , GRpcExceptionScope scope ){
}
@ GRpcExceptionHandler
public Status handle ( IllegalArgumentException exc , GRpcExceptionScope scope ){
}
}
@ GRpcServiceAdvice
class MyHandler2 {
@ GRpcExceptionHandler
public Status anotherHandler ( NullPointerException npe , GRpcExceptionScope scope ){
}
}
Puede tener tantos beans advice
y métodos de controlador como desee, siempre y cuando no interfieran entre sí y no creen ambigüedad en el tipo de excepción manejada.
El bean de servicio grpc
también se descubre para los controladores de errores y tiene mayor prioridad que los métodos de manejo de errores globales descubiertos en los beans @GRpcServiceAdvice
. Los métodos de manejo de errores a nivel de servicio se consideran private
y se invocan solo cuando este servicio genera la excepción:
class SomeException extends Exception {
}
class SomeRuntimeException extends RuntimeException {
}
@ GRpcService
public class HelloService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
...
throw new GRpcRuntimeExceptionWrapper ( new SomeException ()) ; // (1)
//or
throw new GRpcRuntimeExceptionWrapper ( new SomeException (), "myHint" ); // (2)
//or
throw new SomeRuntimeException (); //(3)
}
@ GRpcExceptionHandler
public Status privateHandler ( SomeException npe , GRpcExceptionScope scope ){
// INVOKED when thrown from HelloService service
String myHint = scope . getHintAs ( String . class ); // (4)
scope . getResponseHeaders (). put ( Metadata . Key . of ( "custom" , Metadata . ASCII_STRING_MARSHALLER ), "Value" ); // (5)
}
@ GRpcExceptionHandler
public Status privateHandler ( SomeRuntimeException npe , GRpcExceptionScope scope ){
// INVOKED when thrown from HelloService service
}
}
@ GRpcServiceAdvice
class MyHandler {
@ GRpcExceptionHandler
public Status anotherHandler ( SomeException npe , GRpcExceptionScope scope ){
// NOT INVOKED when thrown from HelloService service
}
@ GRpcExceptionHandler
public Status anotherHandler ( SomeRuntimeException npe , GRpcExceptionScope scope ){
// NOT INVOKED when thrown from HelloService service
}
}
Debido a que la naturaleza de la API del servicio grpc
no permite generar una excepción marcada, se proporciona el tipo de excepción de tiempo de ejecución especial para encapsular la excepción marcada. Luego se desenvuelve cuando se busca el método del controlador.
Al lanzar la excepción GRpcRuntimeExceptionWrapper
, también puede pasar el objeto hint
al que luego se puede acceder desde el objeto scope
en el método handler
.
La excepción de tiempo de ejecución se puede lanzar tal cual y no es necesario ajustarla.
Obtenga el objeto de pista.
Envíe encabezados personalizados al cliente.
El error de autenticación se propaga mediante AuthenticationException
y el error de autorización, mediante AccessDeniedException
.
El error de validación se propaga a través de ConstraintViolationException
: para una solicitud fallida, con Status.INVALID_ARGUMENT
como sugerencia, y para una respuesta fallida, con Status.FAILED_PRECONDITION
como sugerencia.
La demostración está aquí
Gracias al soporte de configuración de Bean Validation a través del descriptor de implementación XML, es posible proporcionar las restricciones para las clases generadas a través de XML en lugar de instrumentar los mensajes generados con un compilador protoc
personalizado.
Agregue la dependencia org.springframework.boot:spring-boot-starter-validation
a su proyecto.
Cree archivos META-INF/validation.xml
y declaraciones de restricciones. (IntelliJ IDEA tiene un excelente soporte de autocompletar para autorizar archivos xml de restricciones de validación de beans)
Vea también ejemplos de la documentación del validador Hibernate
.
Puede encontrar la configuración de demostración y las pruebas correspondientes aquí
Tenga en cuenta que tanto los mensajes request
como response
se están validando.
Si su método gRPC usa el mismo tipo de mensaje de solicitud y respuesta, puede usar los grupos de validación org.lognet.springboot.grpc.validation.group.RequestMessage
y org.lognet.springboot.grpc.validation.group.ResponseMessage
para aplicar una lógica de validación diferente :
...
< getter name = " someField " >
<!-- should be empty for request message -->
< constraint annotation = " javax.validation.constraints.Size " >
< groups >
< value >org.lognet.springboot.grpc.validation.group.RequestMessage</ value > (1)
</ groups >
< element name = " min " >0</ element >
< element name = " max " >0</ element >
</ constraint >
<!-- should NOT be empty for response message -->
< constraint annotation = " javax.validation.constraints.NotEmpty " >
< groups >
< value >org.lognet.springboot.grpc.validation.group.ResponseMessage</ value > (2)
</ groups >
</ constraint >
</ getter >
...
Aplique esta restricción solo para el mensaje request
Aplicar esta restricción solo para el mensaje response
.
Tenga en cuenta también la restricción personalizada entre campos y su uso:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
Como se describe en el capítulo sobre ordenamiento de interceptores, puede darle al interceptor validation
mayor prioridad que al interceptor security
y establecer la propiedad grpc.security.auth.fail-fast
en false
.
En este escenario, si la llamada no está autenticada y no es válida, el cliente obtendrá el estado de respuesta Status.INVALID_ARGUMENT
en lugar del estado Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. La demostración está aquí
Si bien todavía es posible anotar sus métodos rpc con @Transactional
(con spring.aop.proxy-target-class=true
si no está habilitado de forma predeterminada), es probable que se produzca un comportamiento impredecible. Considere a continuación la implementación del método grpc:
@ GRpcService
class MyGrpcService extends ...{
@ Autowired
private MyJpaRepository repo ;
@ Transactional //(1)
public void rpcCall ( Req request , StreamOvserver < Res > observer ) {
Res response = // Database operations via repo
observer . onNext ( response ); //(2)
observer . onCompleted ();
} //(3)
}
El método está anotado como @Transactional
, Spring confirmará la transacción en algún momento después de que regrese el método.
La respuesta se devuelve a la persona que llama.
Los métodos regresan, la transacción finalmente se confirma.
En teoría, y como puede ver, en la práctica, hay un pequeño lapso de tiempo en el que el cliente (si la latencia de la red es mínima y su servidor grpc recomienda el cambio de contexto justo después de <2>) puede intentar acceder a la base de datos a través de otra llamada grpc antes. la transacción está comprometida.
La solución para superar esta situación es externalizar la lógica transaccional en una clase de servicio separada:
@ Service
class MyService {
@ Autowired
private MyJpaRepository repo ;
@ Transactional //(1)
public Res doTransactionalWork (){
// Database operations via repo
return result ;
} //(2)
}
@ GRpcService
class MyGrpcService extends ...{
@ Autowired
private MyService myService ;
public void rpcCall ( Req request , StreamOvserver < Res > observer ) {
Res response = myService . doTransactionalWork ();
observer . onNext ( response ); //(3)
observer . onCompleted ();
}
}
El método de servicio es transaccional.
La transacción finalmente se confirma.
Responder después de confirmar la transacción.
Al seguir este enfoque, también se desacopla la capa de transporte y la lógica empresarial que ahora se pueden probar por separado.
Esquema | Dependencias |
---|---|
Básico |
|
Portador |
|
Costumbre |
|
La configuración de seguridad de GRPC sigue los mismos principios y API que la configuración de seguridad de Spring WEB; está habilitada de forma predeterminada si tiene la dependencia org.springframework.security:spring-security-config
en su classpath.
Puede usar la anotación @Secured
en servicios/métodos para proteger sus puntos finales, o usando API y anulando los valores predeterminados (que precede a la anotación @Secured
):
@ Configuration
class MySecurityCfg extends GrpcSecurityConfigurerAdapter {
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
MethodsDescriptor <?,?> adminMethods = MyServiceGrpc . getSomeMethod ();
builder
. authorizeRequests ()
. methods ( adminMethods ). hasAnyRole ( "admin" )
. anyMethodExcluding ( adminMethods ). hasAnyRole ( "user" )
. withSecuredAnnotation ();( 1 )
}
}
o combine API
con anotaciones @Secured
.
Esta configuración predeterminada protege los métodos/servicios GRPC anotados con la anotación org.springframework.security.access.annotation.@Secured
.
Dejar el valor de la anotación vacío ( @Secured({})
) significa: solo authenticate
, no se realizará ninguna autorización.
Si el bean JwtDecoder
existe en su contexto, también registrará JwtAuthenticationProvider
para manejar la validación del reclamo de autenticación.
BasicAuthSchemeSelector
y BearerTokenAuthSchemeSelector
también se registran automáticamente para admitir la autenticación con nombre de usuario/contraseña y token de portador.
Al configurar grpc.security.auth.enabled
en false
, se puede desactivar la seguridad de GRPC.
La personalización de la configuración de seguridad de GRPC se realiza ampliando GrpcSecurityConfigurerAdapter
(aquí se encuentran varios ejemplos de configuración y escenarios de prueba).
@ Configuration
public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
@ Autowired
private JwtDecoder jwtDecoder ;
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
builder . authorizeRequests ()( 1 )
. methods ( GreeterGrpc . getSayHelloMethod ()). hasAnyAuthority ( "SCOPE_profile" )( 2 )
. and ()
. authenticationProvider ( JwtAuthProviderFactory . withAuthorities ( jwtDecoder ));( 3 )
}
}
Obtener el objeto de configuración de autorización
MethodDefinition
del método sayHello
está permitido para usuarios autenticados con autoridad SCOPE_profile
.
Utilice JwtAuthenticationProvider
para validar el reclamo del usuario (token BEARER
) contra el servidor de recursos configurado con la propiedad spring.security.oauth2.resourceserver.jwt.issuer-uri
.
Es posible conectar su propio proveedor de autenticación personalizado implementando la interfaz AuthenticationSchemeSelector
.
@ Configuration
public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
builder . authorizeRequests ()
. anyMethod (). authenticated () //(1)
. and ()
. authenticationSchemeSelector ( new AuthenticationSchemeSelector () { //(2)
@ Override
public Optional < Authentication > getAuthScheme ( CharSequence authorization ) {
return new MyAuthenticationObject (); // (3)
}
})
. authenticationProvider ( new AuthenticationProvider () { // (4)
@ Override
public Authentication authenticate ( Authentication authentication ) throws AuthenticationException {
MyAuthenticationObject myAuth = ( MyAuthenticationObject ) authentication ;
//validate myAuth
return MyValidatedAuthenticationObject ( withAuthorities ); //(5)
}
@ Override
public boolean supports ( Class <?> authentication ) {
return MyAuthenticationObject . class . isInstance ( authentication );
}
});
}
}
Asegure todos los métodos de servicios.
Registre su propio AuthenticationSchemeSelector
.
Basado en el encabezado de autorización proporcionado: devolver el objeto Authentication
como reclamo (aún no autenticado)
Registre su propio AuthenticationProvider
que admita la validación de MyAuthenticationObject
Validar authentication
proporcionada y devolver el objeto Authentication
validado y autenticado
AuthenticationSchemeSelector
también se puede registrar definiendo Spring Bean en el contexto de su aplicación:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
La sección de soporte de configuración del lado del cliente explica cómo aprobar un esquema de autorización personalizado y reclamar desde el cliente GRPC.
A partir de la versión 4.5.9
también puede utilizar anotaciones estándar @PreAuthorize
y @PostAuthorize
en los métodos y tipos de servicio grpc.
Tipo de llamada | Referencia de objeto de entrada | Referencia de objeto de salida | Muestra |
---|---|---|---|
unario | Por nombre de parámetro | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
flujo de entrada, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
Solicitud única, | Por nombre de parámetro | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
corriente bidi | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
Para obtener el objeto Authentication
en la implementación del método seguro , utilice el siguiente fragmento
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
A partir de 4.5.6
, el objeto Authentication
también se puede obtener a través de la API Spring estándar:
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
Al agregar la dependencia io.github.lognet:grpc-client-spring-boot-starter
a su aplicación cliente java grpc, puede configurar fácilmente las credenciales por canal o por llamada:
class MyClient {
public void doWork (){
final AuthClientInterceptor clientInterceptor = new AuthClientInterceptor (( 1 )
AuthHeader . builder ()
. bearer ()
. binaryFormat ( true )( 3 )
. tokenSupplier ( this :: generateToken )( 4 )
);
Channel authenticatedChannel = ClientInterceptors . intercept (
ManagedChannelBuilder . forAddress ( "host" , 6565 ). build (), clientInterceptor ( 2 )
);
// use authenticatedChannel to invoke GRPC service
}
private ByteBuffer generateToken (){ ( 4 )
// generate bearer token against your resource server
}
}
Crear interceptor de cliente
canal de intercepción
Activar/desactivar el formato binario:
Cuando true
, el encabezado de autenticación se envía con la clave Authorization-bin
mediante un analizador binario.
Cuando false
, el encabezado de autenticación se envía con la clave Authorization
mediante el analizador ASCII.
Proporcionar función de generador de tokens (consulte, por ejemplo).
class MyClient {
public void doWork (){
AuthCallCredentials callCredentials = new AuthCallCredentials ( ( 1 )
AuthHeader . builder (). basic ( "user" , "pwd" . getBytes ())
);
final SecuredGreeterGrpc . SecuredGreeterBlockingStub securedFutureStub = SecuredGreeterGrpc . newBlockingStub ( ManagedChannelBuilder . forAddress ( "host" , 6565 ));( 2 )
final String reply = securedFutureStub
. withCallCredentials ( callCredentials )( 3 )
. sayAuthHello ( Empty . getDefaultInstance ()). getMessage ();
}
}
Crear credenciales de llamada con esquema básico
Crear código auxiliar de servicio
Adjuntar credenciales de llamada a la llamada
AuthHeader
también podría crearse con un esquema de autorización personalizado:
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
El iniciador registra la implementación predeterminada de HealthServiceImpl.
Puede proporcionar el suyo propio registrando el bean ManagedHealthStatusService en el contexto de su aplicación.
Si tiene org.springframework.boot:spring-boot-starter-actuator
y org.springframework.boot:spring-boot-starter-web
en el classpath, el iniciador expondrá:
indicador de salud grpc
en /actuator/health
endpoint.
/actuator/grpc
.
Esto se puede controlar mediante puntos finales estándar y configuración de estado.
A partir de la versión 3.3.0
, el iniciador registrará automáticamente el servidor grpc en ejecución en el registro de Consul si org.springframework.cloud:spring-cloud-starter-consul-discovery
está en classpath y spring.cloud.service-registry.auto-registration.enabled
NO está configurado en false
.
El nombre del servicio registrado tendrá el prefijo grpc-
, es decir, grpc-${spring.application.name}
para no interferir con el nombre del servicio web registrado estándar si elige ejecutar servidores Web
y Grpc
integrados.
ConsulDiscoveryProperties
están vinculados a las propiedades de configuración con el prefijo spring.cloud.consul.discovery
y luego los valores se sobrescriben con las propiedades con el prefijo grpc.consul.discovery
(si están establecidas). Esto le permite tener una configuración de descubrimiento de cónsul separada para los servicios rest
y grpc
si elige exponer ambos desde su aplicación.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
Tanto los servicios rest
como grpc
están registrados con metadatos myKey=myValue
Los servicios de descanso están registrados en myWebTag
Los servicios de Grpc están registrados con myGrpcTag
Configurar spring.cloud.consul.discovery.register-health-check
(o grpc.consul.discovery.register-health-check
) en true
registrará el servicio de verificación de estado GRPC con Consul.
Hay 4 modos de registro admitidos:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(predeterminado)
En este modo, el servidor grpc en ejecución se registra como servicio único con verificación grpc
única con serviceId
vacío.
Tenga en cuenta que la implementación predeterminada no hace nada y simplemente devuelve el estado SERVING
. Es posible que desee proporcionar su implementación de verificación de estado personalizada para este modo.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
En este modo, el servidor grpc en ejecución se registra como servicio único con verificación por cada servicio grpc
descubierto.
STANDALONE_SERVICES
En este modo, cada servicio grpc descubierto se registra como servicio único con verificación única. Cada servicio registrado está etiquetado con su propio nombre de servicio.
NOOP
: no hay servicios grpc registrados. Este modo es útil si ofrece servicios rest
y grpc
en su aplicación, pero por alguna razón, solo los servicios rest
deben registrarse en Consul.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Al crear servicios listos para producción, se recomienda tener un proyecto separado para la API gRPC de su(s) servicio(s) que contenga solo clases protogeneradas tanto para uso del lado del servidor como del cliente.
Luego agregará este proyecto como dependencia implementation
a sus proyectos de gRPC client
y gRPC server
.
Para integrar Eureka
simplemente siga la excelente guía de Spring.
A continuación se muestran las partes esenciales de las configuraciones para proyectos de servidor y cliente.
Agregue eureka starter como dependencia de su proyecto de servidor junto con las clases generadas a partir de archivos proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Configure el servidor gRPC para registrarse en Eureka.
spring :
application :
name : my-service-name (1)
ServiceId
de Eureka de forma predeterminada es el nombre de la aplicación Spring; proporciónelo antes de que el servicio se registre en Eureka.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
Especifique el número de puerto en el que escucha el gRPC.
Registre el puerto de servicio eureka para que sea el mismo que grpc.port
para que el cliente sepa a dónde enviar las solicitudes.
Especifique la URL de registro para que el servicio se registre.
Exponga el servicio gRPC como parte de la aplicación Spring Boot.
@ SpringBootApplication
@ EnableEurekaClient
public class EurekaGrpcServiceApp {
@ GRpcService
public static class GreeterService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
}
}
public static void main ( String [] args ) {
SpringApplication . run ( DemoApp . class , args );
}
}
Agregue eureka starter como dependencia del proyecto de su cliente junto con las clases generadas a partir de archivos proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Configure el cliente para encontrar el registro del servicio eureka:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
si este proyecto no está destinado a actuar como un servicio para otro cliente.
Especifique la URL del registro, para que este cliente sepa dónde buscar el servicio requerido.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
Utilice EurekaClient para obtener las coordenadas de la instancia del servicio gRPC de Eureka y consumir el servicio:
@ EnableEurekaClient
@ Component
public class GreeterServiceConsumer {
@ Autowired
private EurekaClient client ;
public void greet ( String name ) {
final InstanceInfo instanceInfo = client . getNextServerFromEureka ( "my-service-name" , false ); //(1)
final ManagedChannel channel = ManagedChannelBuilder . forAddress ( instanceInfo . getIPAddr (), instanceInfo . getPort ())
. usePlaintext ()
. build (); //(2)
final GreeterServiceGrpc . GreeterServiceFutureStub stub = GreeterServiceGrpc . newFutureStub ( channel ); //(3)
stub . greet ( name ); //(4)
}
}
Obtenga la información sobre la instancia my-service-name
.
Construya channel
en consecuencia.
Crear código auxiliar usando el channel
.
Invocar el servicio.
apache 2.0