Configura automaticamente e executa o servidor gRPC integrado com beans habilitados para @GRpcService como parte do aplicativo spring-boot (vídeo curto)
Os usuários Gradle
são aconselhados a aplicar o plugin:
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
O plugin io.github.lognet.grpc-spring-boot gradle simplifica drasticamente a configuração do projeto.
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 '
}
Por padrão, o starter puxa io.grpc:grpc-netty-shaded
como dependência transitiva, se você for forçado a usar a dependência 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)
Certifique-se de obter a versão que corresponde ao lançamento.
A presença de ambas as bibliotecas no caminho de classe também é suportada pela propriedade grpc.netty-server.on-collision-prefer-shaded-netty
.
Se você estiver usando o plugin Spring Boot Dependency Management, ele pode não extrair a mesma versão com a qual a versão iniciada foi compilada, causando problema de incompatibilidade binária.
Neste caso, você precisará definir de forma forçada e explícita a versão grpc
a ser usada (veja a matriz de versão aqui):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
As notas de lançamento com matriz de compatibilidade podem ser encontradas aqui |
Siga este guia para gerar stub e interface(s) de servidor a partir de seu(s) arquivo(s) .proto
.
Se você estiver empilhando com maven - use este link.
Anote suas implementações de interface de servidor com @org.lognet.springboot.grpc.GRpcService
Opcionalmente, configure a porta do servidor em seu application.yml/properties
. A porta padrão é 6565
.
grpc :
port : 6565
Uma porta aleatória pode ser definida configurando a porta como 0 .A porta real que está sendo usada pode então ser recuperada usando a anotação @LocalRunningGrpcPort no campo int que injetará a porta em execução (configurada explicitamente ou selecionada aleatoriamente) |
Opcionalmente, habilite a reflexão do servidor (consulte https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md)
grpc :
enableReflection : true
Opcionalmente, defina a ordem da fase de inicialização (o padrão é Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
Opcionalmente, defina o número de segundos de espera para que as chamadas pré-existentes sejam concluídas durante o encerramento normal do servidor. Novas chamadas serão rejeitadas durante esse período. Um valor negativo equivale a um período de carência infinito. O valor padrão é 0
(significa não esperar).
grpc :
shutdownGrace : 30
As propriedades específicas do servidor Netty podem ser especificadas no prefixo grpc.netty-server
.
Ao configurar um dos valores grpc.netty-server.xxxx
você está implicitamente definindo o transporte como baseado em 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)
As propriedades do tipo de Duration
podem ser configuradas com o formato de valor de string descrito aqui.
As propriedades do tipo DataSize
podem ser configuradas com o valor da string descrito aqui
Exposto no IP da rede externa com porta personalizada.
Formato do valor da string das propriedades do tipo SocketAddress
:
host:port
(se o valor port
for menor que 1, usa valor aleatório)
host:
(usa a porta grpc padrão, 6565
)
Exposto no IP da rede interna também com porta predefinida 6767
.
Caso você tenha bibliotecas netty shaded
e pure
em dependências, escolha o tipo NettyServerBuilder
que deve ser criado. Este é o tipo que será passado para GRpcServerBuilderConfigurer
(consulte Configuração personalizada do servidor gRPC), o padrão é true
(ou seja, io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
se false
)
O starter também suporta o in-process server
, que deve ser usado para fins de teste:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
Desativa o servidor padrão ( NettyServer
).
Ativa o servidor in-process
.
Se você ativar o NettyServer e o servidor in-process , ambos compartilharão a mesma instância de HealthStatusManager e GRpcServerBuilderConfigurer (consulte Configuração personalizada do servidor gRPC). |
No projeto grpc-spring-boot-starter-demo
você pode encontrar exemplos totalmente funcionais com testes de integração.
A definição de serviço do arquivo .proto
é semelhante a esta:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
Observe a classe io.grpc.examples.GreeterGrpc.GreeterImplBase
gerada que estende io.grpc.BindableService
.
Tudo que você precisa fazer é anotar a implementação do seu serviço com @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 ();
}
}
O starter suporta o registro de dois tipos de interceptadores: Global e Por Serviço .
Em ambos os casos o interceptor deve implementar a interface io.grpc.ServerInterceptor
.
Por serviço
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
LogInterceptor
será instanciado via spring factory se houver bean do tipo LogInterceptor
, ou via construtor no-args caso contrário.
Global
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
A anotação no método java config factory também é suportada:
@ 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 );
}
};
}
}
O serviço específico também tem a oportunidade de desabilitar os interceptadores globais:
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Os interceptores globais podem ser ordenados usando as anotações @Ordered
ou @Priority
do Spring. Seguindo a semântica de ordenação do Spring, valores de ordem inferior têm prioridade mais alta e serão executados primeiro na cadeia de interceptadores.
@ 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
}
O starter usa interceptores integrados para implementar tratamento de erros, Spring Security
, Validation
e integração Metrics
. Sua ordem também pode ser controlada pelas propriedades abaixo:
grpc.recovery.interceptor-order
(erro no tratamento da ordem do interceptador, o padrão é Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
(o padrão é Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
(o padrão é Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
(o padrão é Ordered.HIGHEST_PRECEDENCE+20
)
Isso lhe dá a capacidade de configurar a ordem desejada dos interceptores integrados e personalizados.
Continue lendo!!! Há mais
A forma como o interceptor grpc funciona é interceptando a chamada e retornando o ouvinte de chamada do servidor, que por sua vez também pode interceptar a mensagem de solicitação, antes de encaminhá-la para o manipulador de chamada de serviço real:
Ao definir a propriedade grpc.security.auth.fail-fast
como false
todos os interceptores downstream, bem como todos os interceptores upstream (On_Message) ainda serão executados em caso de falha de autenticação/autorização
Supondo que interceptor_2
seja securityInterceptor
:
Para autenticação/autorização com falha com grpc.security.auth.fail-fast=true
(padrão):
Para autenticação/autorização com falha com grpc.security.auth.fail-fast=false
:
Este início é suportado nativamente pelo projeto spring-cloud-sleuth
.
Continue investigando a integração do grpc.
Ao incluir a dependência org.springframework.boot:spring-boot-starter-actuator
, o starter coletará métricas do servidor gRPC, divididas por
method
- método de serviço gRPC FQN (nome totalmente qualificado)
result
- código de status de resposta
address
- endereço local do servidor (se você expôs endereços de escuta adicionais, com a propriedade grpc.netty-server.additional-listen-addresses
)
Depois de configurar o exportador de sua escolha, você deverá ver o timer
denominado grpc.server.calls
.
Ao definir o bean GRpcMetricsTagsContributor
no contexto do aplicativo, é possível incluir tags customizadas no cronômetro grpc.server.calls
.
Você também pode usar o bean RequestAwareGRpcMetricsTagsContributor
para marcar chamadas unárias e de streaming .
A demonstração está aqui
Mantenha a dispersão baixa para não explodir a cardinalidade da métrica. |
RequestAwareGRpcMetricsTagsContributor
ainda poderá ser executado para autenticação com falha se o interceptador metric
tiver precedência mais alta que o interceptor security
e grpc.security.auth.fail-fast
definido como false
.
Este caso é coberto por este teste.
Certifique-se de ler o capítulo sobre pedidos de interceptores. |
Certifique-se de incluir as dependências abaixo:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
Configuração:
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
Os endpoints padrão /actuator/metrics
e /actuator/prometheus
renderizarão métricas grpc.server.calls
(veja a demonstração aqui).
Proposta de desmantelamento de GRPC |
O starter pode ser configurado automaticamente para validar mensagens de serviço gRPC de solicitação/resposta. Continue em Implementando a validação de mensagens para obter detalhes de configuração.
O starter define internamente o bean do tipo java.util.function.Consumer
que está sendo considerado para registro de função quando spring-cloud-stream
está no classpath, o que é indesejável ( spring-cloud-stream
registra automaticamente o canal se você tiver exatamente um bean Consumidor/Fornecedor/Função no contexto do aplicativo, então você já terá um se usar este iniciador junto com spring-cloud-stream
).
De acordo com isso, é recomendado usar a propriedade spring.cloud.function.definition
em aplicativos prontos para produção e não confiar na descoberta automática.
Consulte a demonstração do GRPC Kafka Stream, a parte essencial é esta linha.
O starter fornece suporte integrado para autenticação e autorização de usuários, aproveitando a integração com a estrutura Spring Security.
Consulte as seções sobre Spring Security Integration para obter detalhes sobre provedores de autenticação suportados e opções de configuração.
A segurança do transporte pode ser configurada usando o certificado raiz junto com seu caminho de chave 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
O valor de ambas as propriedades está no formato suportado pelo ResourceEditor.
O lado do cliente deve ser configurado de acordo:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
Este iniciador extrairá a dependência io.netty:netty-tcnative-boringssl-static
por padrão para oferecer suporte a SSL.
Se você precisar de outro suporte SSL/TLS, exclua esta dependência e siga o Guia de segurança.
Se um ajuste mais detalhado for necessário para configuração de segurança, use o configurador personalizado descrito em Configuração personalizada do servidor gRPC |
Para interceptar a instância io.grpc.ServerBuilder
usada para construir o io.grpc.Server
, você pode adicionar o bean que herda de org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
ao seu contexto e substituir o método configure
.
Vários configuradores também são suportados.
No momento da invocação do método configure
, todos os serviços descobertos, incluindo seus interceptadores, foram adicionados ao construtor passado.
Na implementação do método configure
, você pode adicionar sua configuração 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 ) ;
}
};
}
Se você ativar NettyServer e os servidores in-process , o método configure será invocado na mesma instância do configurador.Se você precisar diferenciar entre os serverBuilder passados, poderá verificar o tipo.Esta é a limitação atual. |
GRpcServerInitializedEvent
é publicado na inicialização do servidor, você pode consumi-lo usando a API regular do Spring.
A partir da versão 5.1.0
, spring-boot-starter-gradle-plugin integra o plugin reactive-grpc protoc do SalesForce:
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
Aqui estão os testes e o serviço de amostra grpc reativo.
O starter registra o GRpcExceptionHandlerInterceptor
que é responsável por propagar a exceção lançada pelo serviço para os manipuladores de erros.
O método de tratamento de erros pode ser registrado tendo o bean anotado @GRpcServiceAdvice
com métodos anotados com anotações @GRpcExceptionHandler
.
Eles são considerados manipuladores de erros global
e o método com parâmetro de tipo de exceção mais próximo pela hierarquia de tipos da exceção lançada é invocado.
A assinatura do manipulador de erros deve seguir o padrão abaixo:
Tipo de retorno | Parâmetro 1 | Parâmetro 2 |
---|---|---|
io.grpc.Status | qualquer tipo de | GRpcExceptionScope |
@ 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 ){
}
}
Você pode ter quantos beans advice
e métodos manipuladores desejar, desde que eles não interfiram entre si e não criem ambiguidade de tipo de exceção tratada.
O bean de serviço grpc
também é descoberto para manipuladores de erros, tendo maior precedência do que os métodos globais de tratamento de erros descobertos nos beans @GRpcServiceAdvice
. Os métodos de tratamento de erros no nível de serviço são considerados private
e invocados somente quando a exceção é lançada por este serviço:
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
}
}
Devido à natureza da API do serviço grpc
que não permite lançar exceções verificadas, o tipo de exceção de tempo de execução especial é fornecido para agrupar a exceção verificada. Em seguida, ele é desembrulhado ao procurar o método do manipulador.
Ao lançar a exceção GRpcRuntimeExceptionWrapper
, você também pode passar o objeto hint
que pode ser acessado a partir do objeto scope
no método handler
.
A exceção de tempo de execução pode ser lançada como está e não precisa ser agrupada.
Obtenha o objeto de dica.
Envie cabeçalhos personalizados para o cliente.
A falha de autenticação é propagada via AuthenticationException
e a falha de autorização - via AccessDeniedException
.
A falha de validação é propagada via ConstraintViolationException
: para solicitação com falha - com Status.INVALID_ARGUMENT
como dica e para resposta com falha - com Status.FAILED_PRECONDITION
como dica.
A demonstração está aqui
Graças ao suporte de configuração do Bean Validation via descritor de implantação XML, é possível fornecer as restrições para classes geradas via XML em vez de instrumentar as mensagens geradas com o compilador protoc
customizado.
Adicione a dependência org.springframework.boot:spring-boot-starter-validation
ao seu projeto.
Crie META-INF/validation.xml
e arquivo(s) de declaração de restrições. (IntelliJ IDEA tem excelente suporte de preenchimento automático para autorizar arquivos xml de restrições de validação de bean)
Veja também exemplos da documentação do validador Hibernate
Você pode encontrar a configuração de demonstração e os testes correspondentes aqui
Observe que as mensagens request
e response
estão sendo validadas.
Se o seu método gRPC usar o mesmo tipo de mensagem de solicitação e resposta, você poderá usar grupos de validação org.lognet.springboot.grpc.validation.group.RequestMessage
e org.lognet.springboot.grpc.validation.group.ResponseMessage
para aplicar diferentes lógicas de validação :
...
< 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 restrição apenas para mensagem request
Aplique esta restrição apenas para mensagem response
Observe também a restrição de campo cruzado personalizada e seu uso:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
Conforme descrito no capítulo Ordenação de interceptores, você pode dar ao interceptor validation
uma precedência mais alta do que o interceptor security
e definir a propriedade grpc.security.auth.fail-fast
como false
.
Neste cenário, se a chamada for não autenticada e inválida, o cliente obterá Status.INVALID_ARGUMENT
em vez do status de resposta Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. A demonstração está aqui
Embora ainda seja possível anotar seus métodos rpc com @Transactional
(com spring.aop.proxy-target-class=true
se não estiver habilitado por padrão), há chances de obter um comportamento imprevisível. Considere abaixo a implementação do 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)
}
O método é anotado como @Transactional
, o Spring irá confirmar a transação em algum momento após o retorno do método
A resposta é retornada ao chamador
Métodos retornam, transação eventualmente confirmada.
Teoricamente, e como você pode ver - praticamente, há um pequeno intervalo de tempo quando o cliente (se a latência da rede for mínima e seu servidor grpc encorajar a troca de contexto logo após <2>) pode tentar acessar o banco de dados por meio de outra chamada grpc antes a transação é confirmada.
A solução para superar esta situação é externalizar a lógica transacional em uma classe de serviço 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 ();
}
}
O método de serviço é transacional
A transação é eventualmente confirmada.
Responder após a transação ser confirmada.
Seguindo essa abordagem você também dissocia a camada de transporte e a lógica de negócios que agora podem ser testadas separadamente.
Esquema | Dependências |
---|---|
Básico |
|
Portador |
|
Personalizado |
|
A configuração de segurança GRPC segue os mesmos princípios e APIs da configuração de segurança Spring WEB, ela é habilitada por padrão se você tiver dependência org.springframework.security:spring-security-config
em seu caminho de classe.
Você pode usar a anotação @Secured
em serviços/métodos para proteger seus endpoints ou usando API e substituindo padrões (que precedem a anotação @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 )
}
}
ou combine API
com anotações @Secured
.
Esta configuração padrão protege métodos/serviços GRPC anotados com a anotação org.springframework.security.access.annotation.@Secured
.
Deixar o valor da anotação vazio ( @Secured({})
) significa: somente authenticate
, nenhuma autorização será realizada.
Se o bean JwtDecoder
existir em seu contexto, ele também registrará JwtAuthenticationProvider
para lidar com a validação da reivindicação de autenticação.
BasicAuthSchemeSelector
e BearerTokenAuthSchemeSelector
também são registrados automaticamente para suportar autenticação com nome de usuário/senha e token de portador.
Ao definir grpc.security.auth.enabled
como false
, a segurança do GRPC pode ser desativada.
A personalização da configuração de segurança GRPC é feita estendendo GrpcSecurityConfigurerAdapter
(vários exemplos de configuração e cenários de teste estão aqui.)
@ 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 )
}
}
Obtenha o objeto de configuração de autorização
MethodDefinition
do método sayHello
é permitido para usuários autenticados com autoridade SCOPE_profile
.
Use JwtAuthenticationProvider
para validar a declaração do usuário (token BEARER
) em relação ao servidor de recursos configurado com a propriedade spring.security.oauth2.resourceserver.jwt.issuer-uri
.
É possível conectar seu próprio provedor de autenticação personalizado implementando a interface 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 );
}
});
}
}
Proteja todos os métodos de serviços.
Registre seu próprio AuthenticationSchemeSelector
.
Com base no cabeçalho de autorização fornecido - retorne o objeto Authentication
como uma reivindicação (ainda não autenticado)
Registre seu próprio AuthenticationProvider
que suporta validação de MyAuthenticationObject
Validar authentication
fornecida e retornar o objeto Authentication
validado e autenticado
AuthenticationSchemeSelector
também pode ser registrado definindo o Spring bean no contexto do seu aplicativo:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
A seção de suporte à configuração do lado do cliente explica como passar o esquema de autorização personalizado e a reivindicação do cliente GRPC.
A partir da versão 4.5.9
você também pode usar anotações padrão @PreAuthorize
e @PostAuthorize
em métodos de serviço grpc e tipos de serviço grpc.
Tipo de chamada | Referência do objeto de entrada | Referência do objeto de saída | Amostra |
---|---|---|---|
Unário | Por nome de parâmetro | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Fluxo de entrada, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
Pedido único, | Por nome de parâmetro | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Fluxo Bidi | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
Para obter o objeto Authentication
na implementação do método seguro , use o snippet abaixo
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
A partir da 4.5.6
, o objeto Authentication
também pode ser obtido via API Spring padrão:
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
Ao adicionar a dependência io.github.lognet:grpc-client-spring-boot-starter
ao seu aplicativo cliente java grpc, você pode configurar facilmente credenciais por canal ou por chamada:
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
}
}
Criar interceptador de cliente
Canal de interceptação
Ative/desative o formato binário:
Quando true
, o cabeçalho de autenticação é enviado com a chave Authorization-bin
usando o empacotador binário.
Quando false
, o cabeçalho de autenticação é enviado com a chave Authorization
usando o empacotador ASCII.
Fornece função de gerador de token (consulte, por exemplo).
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 ();
}
}
Crie credenciais de chamada com esquema básico
Criar stub de serviço
Anexe credenciais de chamada à chamada
AuthHeader
também pode ser construído com esquema de autorização personalizado:
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
O starter registra a implementação padrão de HealthServiceImpl.
Você pode fornecer o seu próprio registrando o bean ManagedHealthStatusService no contexto do seu aplicativo.
Se você tiver org.springframework.boot:spring-boot-starter-actuator
e org.springframework.boot:spring-boot-starter-web
no caminho de classe, o starter irá expor:
indicador de integridade grpc
em /actuator/health
endpoint.
/actuator/grpc
ponto de extremidade.
Isso pode ser controlado por endpoints padrão e configuração de integridade.
A partir da versão 3.3.0
, o starter registrará automaticamente o servidor grpc em execução no registro Consul se org.springframework.cloud:spring-cloud-starter-consul-discovery
estiver no classpath e spring.cloud.service-registry.auto-registration.enabled
NÃO está definido como false
.
O nome do serviço registrado será prefixado com grpc-
, ou seja, grpc-${spring.application.name}
para não interferir no nome do serviço web registrado padrão se você optar por executar servidores Grpc
e Web
incorporados.
ConsulDiscoveryProperties
são vinculados a propriedades de configuração prefixadas por spring.cloud.consul.discovery
e, em seguida, os valores são substituídos por propriedades prefixadas grpc.consul.discovery
(se definidas). Isso permite que você tenha uma configuração de descoberta consul separada para serviços rest
e grpc
se você optar por expor ambos em seu aplicativo.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
Os serviços rest
e grpc
são registrados com metadados myKey=myValue
Os serviços de descanso são registrados em myWebTag
Os serviços Grpc são registrados em myGrpcTag
Definir spring.cloud.consul.discovery.register-health-check
(ou grpc.consul.discovery.register-health-check
) como true
registrará o serviço de verificação de integridade GRPC no Consul.
Existem 4 modos de registro suportados:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(padrão)
Neste modo, o servidor grpc em execução é registrado como serviço único com verificação grpc
única com serviceId
vazio.
Observe que a implementação padrão não faz nada e simplesmente retorna o status SERVING
. Talvez você queira fornecer sua implementação personalizada de verificação de integridade para esse modo.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Neste modo, o servidor grpc em execução é registrado como serviço único com verificação para cada serviço grpc
descoberto.
STANDALONE_SERVICES
Neste modo, cada serviço grpc descoberto é registrado como serviço único com verificação única. Cada serviço registrado é marcado com seu próprio nome de serviço.
NOOP
- nenhum serviço grpc registrado. Este modo é útil se você servir serviços rest
e grpc
em seu aplicativo, mas por algum motivo, apenas serviços rest
devem ser registrados no Consul.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Ao construir serviços prontos para produção, o conselho é ter um projeto separado para sua API gRPC de serviço(s) que contenha apenas classes geradas por proto, tanto para uso no servidor quanto no cliente.
Em seguida, você adicionará este projeto como dependência implementation
aos projetos do gRPC client
e gRPC server
.
Para integrar Eureka
basta seguir o ótimo guia do Spring.
Abaixo estão as partes essenciais das configurações para projetos de servidor e cliente.
Adicione o eureka starter como dependência do seu projeto de servidor junto com as classes geradas a partir dos arquivos proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Configure o servidor gRPC para se registrar no Eureka.
spring :
application :
name : my-service-name (1)
ServiceId
do Eureka por padrão é o nome do aplicativo Spring, forneça-o antes que o serviço se registre no Eureka.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
Especifique o número da porta em que o gRPC está escutando.
Registre a porta de serviço eureka como grpc.port
para que o cliente saiba para onde enviar as solicitações.
Especifique o URL do registro, para que o serviço se registre.
Exponha o serviço gRPC como parte do aplicativo 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 );
}
}
Adicione o eureka starter como dependência do seu projeto cliente junto com as classes geradas a partir dos arquivos proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Configure o cliente para localizar o registro do serviço eureka:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
se este projeto não se destina a funcionar como um serviço para outro cliente.
Especifique a URL do registro, para que este cliente saiba onde procurar o serviço necessário.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
Use EurekaClient para obter as coordenadas da instância de serviço gRPC de Eureka e consumir o serviço:
@ 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)
}
}
Obtenha as informações sobre a instância my-service-name
.
Crie channel
de acordo.
Crie um stub usando o channel
.
Invoque o serviço.
Apache 2.0