Konfiguriert den eingebetteten gRPC-Server automatisch und führt ihn mit @GRpcService-fähigen Beans als Teil der Spring-Boot-Anwendung aus (kurzes Video)
Gradle
Benutzern wird empfohlen, das Plugin zu verwenden:
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
Das Gradle-Plugin io.github.lognet.grpc-spring-boot vereinfacht die Projekteinrichtung erheblich.
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 '
}
Standardmäßig ruft der Starter io.grpc:grpc-netty-shaded
als transitive Abhängigkeit ab, wenn Sie gezwungen sind, eine reine grpc-netty
-Abhängigkeit zu verwenden:
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)
Stellen Sie sicher, dass Sie die Version abrufen, die der Veröffentlichung entspricht.
Die Präsenz beider Bibliotheken im Klassenpfad wird auch mit der Eigenschaft grpc.netty-server.on-collision-prefer-shaded-netty
unterstützt.
Wenn Sie das Spring Boot Dependency Management-Plugin verwenden, ruft es möglicherweise nicht die gleiche Version ab, mit der die gestartete Version kompiliert wurde, was zu einem Problem mit der binären Inkompatibilität führt.
In diesem Fall müssen Sie die zu verwendende grpc
Version zwangsweise und explizit festlegen (siehe Versionsmatrix hier):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
Die Release Notes mit Kompatibilitätsmatrix finden Sie hier |
Befolgen Sie diese Anleitung, um Stub- und Serverschnittstellen aus Ihren .proto
Dateien zu generieren.
Wenn Sie mit Maven stapeln, verwenden Sie diesen Link.
Kommentieren Sie Ihre Serverschnittstellenimplementierung(en) mit @org.lognet.springboot.grpc.GRpcService
Konfigurieren Sie optional den Server-Port in Ihrer application.yml/properties
. Der Standardport ist 6565
.
grpc :
port : 6565
Ein zufälliger Port kann definiert werden, indem der Port auf 0 gesetzt wird.Der tatsächlich verwendete Port kann dann mithilfe der Annotation @LocalRunningGrpcPort im int Feld abgerufen werden, die den laufenden Port einfügt (explizit konfiguriert oder zufällig ausgewählt). |
Aktivieren Sie optional die Serverreflexion (siehe https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md).
grpc :
enableReflection : true
Legen Sie optional die Startphasenreihenfolge fest (standardmäßig Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
Legen Sie optional die Anzahl der Sekunden fest, die beim ordnungsgemäßen Herunterfahren des Servers auf die Beendigung bereits vorhandener Anrufe gewartet werden soll. Neue Anrufe werden in dieser Zeit abgewiesen. Ein negativer Wert entspricht einer unendlichen Kulanzfrist. Der Standardwert ist 0
(bedeutet nicht warten).
grpc :
shutdownGrace : 30
Netty-spezifische Servereigenschaften können unter dem Präfix grpc.netty-server
angegeben werden.
Indem Sie einen der grpc.netty-server.xxxx
-Werte konfigurieren, stellen Sie den Transport implizit auf Netty-basiert ein.
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)
Eigenschaften des Typs Duration
können mit dem hier beschriebenen Zeichenfolgewertformat konfiguriert werden.
Die Eigenschaften DataSize
Typs können mit dem hier beschriebenen Zeichenfolgewert konfiguriert werden
Verfügbar auf externer Netzwerk-IP mit benutzerdefiniertem Port.
Zeichenfolgenwertformat für Eigenschaften SocketAddress
-Typs:
host:port
(wenn port
kleiner als 1 ist, wird ein Zufallswert verwendet)
host:
(verwendet den Standard-GrPC-Port 6565
)
Auch auf interner Netzwerk-IP mit vordefiniertem Port 6767
verfügbar.
Wenn Sie sowohl shaded
als auch pure
Netty-Bibliotheken in Abhängigkeiten haben, wählen Sie den NettyServerBuilder
Typ aus, der erstellt werden soll. Dies ist der Typ, der an GRpcServerBuilderConfigurer
übergeben wird (siehe Benutzerdefinierte gRPC-Serverkonfiguration). Der Standardwert ist true
(d. h. io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
, wenn false
).
Der Starter unterstützt auch den in-process server
, der zu Testzwecken verwendet werden sollte:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
Deaktiviert den Standardserver ( NettyServer
).
Aktiviert den in-process
Server.
Wenn Sie sowohl den NettyServer als auch in-process -Server aktivieren, nutzen beide die gleiche Instanz von HealthStatusManager und GRpcServerBuilderConfigurer (siehe Benutzerdefinierte gRPC-Serverkonfiguration). |
Im Projekt grpc-spring-boot-starter-demo
finden Sie voll funktionsfähige Beispiele mit Integrationstests.
Die Dienstdefinition aus .proto
Datei sieht folgendermaßen aus:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
Beachten Sie die generierte Klasse io.grpc.examples.GreeterGrpc.GreeterImplBase
, io.grpc.BindableService
erweitert.
Alles, was Sie tun müssen, ist, Ihre Service-Implementierung mit @org.lognet.springboot.grpc.GRpcService
zu kommentieren
@ 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 ();
}
}
Der Starter unterstützt die Registrierung von zwei Arten von Abfangjägern: Global und Per Service .
In beiden Fällen muss der Interceptor die Schnittstelle io.grpc.ServerInterceptor
implementieren.
Pro Dienst
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
LogInterceptor
wird über die Spring Factory instanziiert, wenn eine Bean vom Typ LogInterceptor
vorhanden ist, andernfalls über den Konstruktor ohne Argumente.
Global
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
Die Anmerkung zur Java-Config-Factory-Methode wird ebenfalls unterstützt:
@ 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 );
}
};
}
}
Der jeweilige Dienst bietet auch die Möglichkeit, die globalen Abfangjäger zu deaktivieren:
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Globale Interceptoren können mithilfe @Ordered
oder @Priority
-Annotationen von Spring bestellt werden. Gemäß der Ordnungssemantik von Spring haben Werte niedrigerer Ordnung eine höhere Priorität und werden zuerst in der Interceptor-Kette ausgeführt.
@ 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
}
Der Starter verwendet integrierte Interceptoren, um Fehlerbehandlung, Spring- Security
, Validation
und Metrics
Integration zu implementieren. Ihre Reihenfolge kann auch durch die folgenden Eigenschaften gesteuert werden:
grpc.recovery.interceptor-order
(Fehlerbehandlungs-Interceptor-Reihenfolge, standardmäßig Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
(standardmäßig Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
(standardmäßig Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
(standardmäßig Ordered.HIGHEST_PRECEDENCE+20
)
Dies gibt Ihnen die Möglichkeit, die gewünschte Reihenfolge der integrierten und benutzerdefinierten Abfangjäger festzulegen.
Lesen Sie weiter!!! Es gibt noch mehr
Die Funktionsweise des grpc-Interceptors besteht darin, dass er den Anruf abfängt und an den Server-Anruf-Listener zurückgibt, der wiederum die Anforderungsnachricht ebenfalls abfangen kann, bevor er sie an den eigentlichen Service-Call-Handler weiterleitet:
Wenn Sie die Eigenschaft grpc.security.auth.fail-fast
auf false
setzen, werden alle Downstream-Interceptoren sowie alle Upstream-Interceptoren (On_Message) im Falle eines Authentifizierungs-/Autorisierungsfehlers weiterhin ausgeführt
Angenommen, interceptor_2
ist securityInterceptor
:
Für fehlgeschlagene Authentifizierung/Autorisierung mit grpc.security.auth.fail-fast=true
(Standard):
Für fehlgeschlagene Authentifizierung/Autorisierung mit grpc.security.auth.fail-fast=false
:
Dieser Start wird nativ vom spring-cloud-sleuth
Projekt unterstützt.
Bitte untersuchen Sie weiterhin die grpc-Integration.
Durch die Einbeziehung der Abhängigkeit org.springframework.boot:spring-boot-starter-actuator
erfasst der Starter gRPC-Servermetriken, aufgeschlüsselt nach
method
– gRPC-Dienstmethode FQN (vollständig qualifizierter Name)
result
– Antwortstatuscode
address
– lokale Serveradresse (wenn Sie zusätzliche Abhöradressen mit der Eigenschaft grpc.netty-server.additional-listen-addresses
verfügbar gemacht haben)
Nachdem Sie den Exporter Ihrer Wahl konfiguriert haben, sollte der timer
mit dem Namen grpc.server.calls
angezeigt werden.
Durch die Definition GRpcMetricsTagsContributor
Bean in Ihrem Anwendungskontext können Sie benutzerdefinierte Tags zum grpc.server.calls
-Timer hinzufügen.
Sie können auch RequestAwareGRpcMetricsTagsContributor
-Bean verwenden, um unäre und Streaming- Aufrufe zu kennzeichnen.
Demo ist da
Halten Sie die Streuung niedrig, um die Kardinalität der Metrik nicht zu sprengen. |
RequestAwareGRpcMetricsTagsContributor
kann weiterhin bei fehlgeschlagener Authentifizierung ausgeführt werden, wenn metric
-Interceptor eine höhere Priorität als security
Interceptor hat und grpc.security.auth.fail-fast
auf false
gesetzt ist.
Dieser Fall wird durch diesen Test abgedeckt.
Lesen Sie unbedingt das Kapitel „Bestellung von Abfangjägern“. |
Stellen Sie sicher, dass Sie die folgenden Abhängigkeiten einbeziehen:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
Konfiguration:
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
Standard- /actuator/metrics
und /actuator/prometheus
-Endpunkte rendern grpc.server.calls
-Metriken (siehe Demo hier).
Vorschlag zur Verschrottung von GRPC |
Der Starter kann automatisch konfiguriert werden, um Anfrage-/Antwort-gRPC-Dienstnachrichten zu validieren. Für Konfigurationsdetails fahren Sie bitte mit „Implementieren der Nachrichtenvalidierung“ fort.
Der Starter definiert intern die Bean vom Typ java.util.function.Consumer
, die für die Funktionsregistrierung in Betracht gezogen wird, wenn sich spring-cloud-stream
im Klassenpfad befindet, was unerwünscht ist ( spring-cloud-stream
registriert den Kanal automatisch, wenn Sie genau das haben). eine Consumer/Supplier/Function-Bean im Anwendungskontext, Sie haben also bereits eine, wenn Sie diesen Starter zusammen mit spring-cloud-stream
verwenden).
Demnach wird empfohlen, die Eigenschaft spring.cloud.function.definition
in produktionsbereiten Anwendungen zu verwenden und sich nicht auf die automatische Erkennung zu verlassen.
Bitte beachten Sie die GRPC Kafka Stream-Demo. Der wesentliche Teil ist diese Zeile.
Der Starter bietet integrierte Unterstützung für die Authentifizierung und Autorisierung von Benutzern und nutzt die Integration mit dem Spring Security-Framework.
Weitere Informationen zu unterstützten Authentifizierungsanbietern und Konfigurationsoptionen finden Sie in den Abschnitten zur Spring Security-Integration.
Die Transportsicherheit kann mithilfe des Root-Zertifikats zusammen mit seinem privaten Schlüsselpfad konfiguriert werden:
grpc :
security :
cert-chain : classpath:cert/server-cert.pem
private-key : file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
Der Wert beider Eigenschaften liegt in der von ResourceEditor unterstützten Form vor.
Die Clientseite sollte entsprechend konfiguriert sein:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
Dieser Starter ruft standardmäßig die Abhängigkeit io.netty:netty-tcnative-boringssl-static
ab, um SSL zu unterstützen.
Wenn Sie eine andere SSL/TLS-Unterstützung benötigen, schließen Sie diese Abhängigkeit bitte aus und folgen Sie dem Sicherheitsleitfaden.
Wenn für die Sicherheitseinrichtung eine detailliertere Abstimmung erforderlich ist, verwenden Sie bitte den benutzerdefinierten Konfigurationsparameter, der unter „Benutzerdefinierte gRPC-Serverkonfiguration“ beschrieben wird |
Um die io.grpc.ServerBuilder
Instanz abzufangen, die zum Erstellen von io.grpc.Server
verwendet wird, können Sie Ihrem Kontext eine Bean hinzufügen, die von org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
erbt, und die configure
-Methode überschreiben.
Es werden auch mehrere Konfiguratoren unterstützt.
Zum Zeitpunkt des Aufrufs der Methode configure
waren alle erkannten Dienste, einschließlich ihrer Interceptors, dem übergebenen Builder hinzugefügt worden.
In Ihrer Implementierung der configure
Methode können Sie Ihre benutzerdefinierte Konfiguration hinzufügen:
@ 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 ) ;
}
};
}
Wenn Sie sowohl NettyServer als auch in-process -Server aktivieren, wird die configure auf derselben Instanz des Konfigurators aufgerufen.Wenn Sie zwischen den übergebenen serverBuilder s unterscheiden müssen, können Sie den Typ überprüfen.Dies ist die aktuelle Einschränkung. |
GRpcServerInitializedEvent
wird beim Serverstart veröffentlicht. Sie können es mit der regulären Spring-API nutzen.
Ab Version 5.1.0
integriert das Spring-Boot-Starter-Gradle-Plugin das Reactive-GrPC-Protokoll-Plugin von SalesForce:
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
Hier sind die Tests und der reaktive GRPC-Beispielservice.
Der Starter registriert den GRpcExceptionHandlerInterceptor
, der für die Weitergabe der vom Dienst ausgelösten Ausnahme an die Fehlerhandler verantwortlich ist.
Die Fehlerbehandlungsmethode könnte registriert werden, indem @GRpcServiceAdvice
mit Methoden versehen wird, die mit @GRpcExceptionHandler
-Annotationen annotiert sind.
Diese gelten als global
Fehlerhandler und die Methode mit dem Ausnahmetypparameter, der der ausgelösten Ausnahme in der Typhierarchie am nächsten kommt, wird aufgerufen.
Die Signatur des Fehlerhandlers muss dem folgenden Muster folgen:
Rückgabetyp | Parameter 1 | Parameter 2 |
---|---|---|
io.grpc.Status | Jeder | 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 ){
}
}
Sie können so viele advice
Beans und Handler-Methoden haben, wie Sie möchten, solange sie sich nicht gegenseitig stören und keine Mehrdeutigkeit der behandelten Ausnahmetypen erzeugen.
Die grpc
-Service-Bean wird auch für Fehlerhandler erkannt und hat eine höhere Priorität als globale Fehlerbehandlungsmethoden, die in @GRpcServiceAdvice
-Beans erkannt werden. Die Fehlerbehandlungsmethoden auf Dienstebene gelten als private
und werden nur aufgerufen, wenn die Ausnahme von diesem Dienst ausgelöst wird:
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
}
}
Da die grpc
Dienst-API das Auslösen einer geprüften Ausnahme nicht zulässt, wird der spezielle Laufzeitausnahmetyp bereitgestellt, um die geprüfte Ausnahme einzuschließen. Es wird dann ausgepackt, wenn nach der Handler-Methode gesucht wird.
Beim Auslösen der GRpcRuntimeExceptionWrapper
-Ausnahme können Sie auch das hint
-Objekt übergeben, auf das dann über das scope
Objekt in der handler
-Methode zugegriffen werden kann.
Die Laufzeitausnahme kann unverändert ausgelöst werden und muss nicht umschlossen werden.
Rufen Sie das Hinweisobjekt ab.
Senden Sie benutzerdefinierte Header an den Client.
Authentifizierungsfehler werden über AuthenticationException
und Autorisierungsfehler über AccessDeniedException
weitergegeben.
Ein Validierungsfehler wird über ConstraintViolationException
weitergegeben: für fehlgeschlagene Anfragen – mit Status.INVALID_ARGUMENT
als Hinweis und für fehlgeschlagene Antworten – mit Status.FAILED_PRECONDITION
als Hinweis.
Die Demo ist hier
Dank der Unterstützung der Bean-Validierung-Konfiguration über den XML-Bereitstellungsdeskriptor ist es möglich, die Einschränkungen für generierte Klassen über XML bereitzustellen, anstatt die generierten Nachrichten mit einem benutzerdefinierten protoc
-Compiler zu instrumentieren.
Fügen Sie Ihrem Projekt die Abhängigkeit org.springframework.boot:spring-boot-starter-validation
hinzu.
Erstellen Sie META-INF/validation.xml
und Einschränkungsdeklarationsdateien. (IntelliJ IDEA bietet hervorragende Unterstützung für die automatische Vervollständigung für die Autorisierung von XML-Dateien mit Bean-Validierungseinschränkungen.)
Siehe auch Beispiele aus der Dokumentation Hibernate
Validator
Demokonfiguration und entsprechende Tests finden Sie hier
Beachten Sie, dass sowohl request
als auch response
validiert werden.
Wenn Ihre gRPC-Methode denselben Anforderungs- und Antwortnachrichtentyp verwendet, können Sie die Validierungsgruppen org.lognet.springboot.grpc.validation.group.RequestMessage
und org.lognet.springboot.grpc.validation.group.ResponseMessage
verwenden, um unterschiedliche Validierungslogik anzuwenden :
...
< 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 >
...
Wenden Sie diese Einschränkung nur für request
an
Wenden Sie diese Einschränkung nur auf response
an
Beachten Sie auch die benutzerdefinierte feldübergreifende Einschränkung und ihre Verwendung:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
Wie im Kapitel „Reihenfolge von Interceptoren“ beschrieben, können Sie validation
Interceptor eine höhere Priorität als security
-Interceptor geben und die Eigenschaft grpc.security.auth.fail-fast
auf false
setzen.
Wenn der Aufruf in diesem Szenario sowohl nicht authentifiziert als auch ungültig ist, erhält der Client Status.INVALID_ARGUMENT
anstelle des Antwortstatus Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. Demo ist da
Es ist zwar immer noch möglich, Ihre RPC-Methoden mit @Transactional
(mit spring.aop.proxy-target-class=true
, wenn es nicht standardmäßig aktiviert ist) mit Anmerkungen zu versehen, es besteht jedoch die Möglichkeit, dass es zu unvorhersehbarem Verhalten kommt. Betrachten Sie die Implementierung der grpc-Methode unten:
@ 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)
}
Die Methode ist als @Transactional
annotiert. Spring wird die Transaktion irgendwann nach der Rückkehr der Methode festschreiben
Die Antwort wird an den Anrufer zurückgegeben
Die Methode kehrt zurück, die Transaktion wird schließlich festgeschrieben.
Theoretisch und wie Sie sehen können, gibt es praktisch nur eine kurze Zeitspanne, in der der Client (wenn die Netzwerklatenz minimal ist und Ihr Grpc-Server den Kontextwechsel direkt nach <2> empfiehlt) zuvor versuchen kann, über einen anderen Grpc-Aufruf auf die Datenbank zuzugreifen Die Transaktion ist festgeschrieben.
Die Lösung zur Überwindung dieser Situation besteht darin, die Transaktionslogik in eine separate Serviceklasse zu externalisieren:
@ 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 ();
}
}
Die Servicemethode ist transaktional
Die Transaktion wird schließlich festgeschrieben.
Antwort, nachdem die Transaktion festgeschrieben wurde.
Durch diesen Ansatz entkoppeln Sie auch die Transportschicht und die Geschäftslogik, die nun separat getestet werden können.
Schema | Abhängigkeiten |
---|---|
Basic |
|
Träger |
|
Brauch |
|
Die GRPC-Sicherheitskonfiguration folgt denselben Prinzipien und APIs wie die Spring WEB-Sicherheitskonfiguration. Sie ist standardmäßig aktiviert, wenn Sie die Abhängigkeit org.springframework.security:spring-security-config
in Ihrem Klassenpfad haben.
Sie können @Secured
-Annotation für Dienste/Methoden verwenden, um Ihre Endpunkte zu schützen, oder indem Sie die API verwenden und Standardeinstellungen überschreiben (die die @Secured
-Annotation vorantreiben):
@ 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 )
}
}
oder kombinieren Sie API
mit @Secured
-Annotationen.
Diese Standardkonfiguration sichert GRPC-Methoden/-Dienste, die mit der Annotation org.springframework.security.access.annotation.@Secured
annotiert sind.
Wenn Sie den Wert der Anmerkung leer lassen ( @Secured({})
), bedeutet dies: nur authenticate
, es wird keine Autorisierung durchgeführt.
Wenn JwtDecoder
Bean in Ihrem Kontext vorhanden ist, registriert sie auch JwtAuthenticationProvider
um die Validierung des Authentifizierungsanspruchs durchzuführen.
BasicAuthSchemeSelector
und BearerTokenAuthSchemeSelector
werden ebenfalls automatisch registriert, um die Authentifizierung mit Benutzername/Passwort und Bearer-Token zu unterstützen.
Durch Festlegen von grpc.security.auth.enabled
auf false
kann die GRPC-Sicherheit deaktiviert werden.
Die Anpassung der GRPC-Sicherheitskonfiguration erfolgt durch Erweiterung GrpcSecurityConfigurerAdapter
(Verschiedene Konfigurationsbeispiele und Testszenarien finden Sie hier.)
@ 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 )
}
}
Besorgen Sie sich das Berechtigungskonfigurationsobjekt
MethodDefinition
der Methode sayHello
ist für authentifizierte Benutzer mit SCOPE_profile
-Berechtigung zulässig.
Verwenden Sie JwtAuthenticationProvider
um den Benutzeranspruch ( BEARER
Token) anhand des Ressourcenservers zu validieren, der mit der Eigenschaft spring.security.oauth2.resourceserver.jwt.issuer-uri
konfiguriert ist.
Durch die Implementierung AuthenticationSchemeSelector
Schnittstelle ist es möglich, Ihren eigenen maßgeschneiderten Authentifizierungsanbieter einzubinden.
@ 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 );
}
});
}
}
Sichern Sie alle Dienstmethoden.
Registrieren Sie Ihren eigenen AuthenticationSchemeSelector
.
Basierend auf dem bereitgestellten Autorisierungsheader – Authentication
als Anspruch zurückgeben (noch nicht authentifiziert)
Registrieren Sie Ihren eigenen AuthenticationProvider
, der die Validierung von MyAuthenticationObject
unterstützt
Validieren Sie die bereitgestellte authentication
und geben Sie das validierte und authentifizierte Authentication
zurück
AuthenticationSchemeSelector
kann auch registriert werden, indem Sie Spring Bean in Ihrem Anwendungskontext definieren:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
Im Abschnitt zur clientseitigen Konfigurationsunterstützung wird erläutert, wie ein benutzerdefiniertes Autorisierungsschema übergeben und vom GRPC-Client beansprucht wird.
Ab Version 4.5.9
können Sie auch die standardmäßigen @PreAuthorize
und @PostAuthorize
Annotationen für grpc-Dienstmethoden und grpc-Diensttypen verwenden.
Anruftyp | Eingabeobjektreferenz | Ausgabeobjektreferenz | Probe |
---|---|---|---|
Unär | Nach Parametername | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Eingabestream, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
Einzelanfrage, | Nach Parametername | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Bidi-Stream | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
Um ein Authentication
in der Implementierung der gesicherten Methode zu erhalten, verwenden Sie bitte das folgende Snippet
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
Ab 4.5.6
kann das Authentication
auch über die Standard-Spring-API abgerufen werden:
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
Durch Hinzufügen der Abhängigkeit io.github.lognet:grpc-client-spring-boot-starter
zu Ihrer Java-Grpc- Clientanwendung können Sie ganz einfach Anmeldeinformationen pro Kanal oder pro Anruf konfigurieren:
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
}
}
Erstellen Sie einen Client-Interceptor
Abfangkanal
Schalten Sie das Binärformat ein/aus:
Bei true
wird der Authentifizierungsheader mit Authorization-bin
-Schlüssel unter Verwendung eines binären Marshallers gesendet.
Bei false
wird der Authentifizierungsheader mit dem Authorization
unter Verwendung des ASCII-Marshallers gesendet.
Bereitstellung einer Token-Generator-Funktion (siehe Beispiel).
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 ();
}
}
Erstellen Sie Anrufanmeldeinformationen mit dem Basisschema
Erstellen Sie einen Service-Stub
Fügen Sie dem Anruf Anrufanmeldeinformationen hinzu
AuthHeader
könnte auch mit einem maßgeschneiderten Autorisierungsschema erstellt werden:
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
Der Starter registriert die Standardimplementierung von HealthServiceImpl.
Sie können Ihre eigene Bean bereitstellen, indem Sie die ManagedHealthStatusService-Bean in Ihrem Anwendungskontext registrieren.
Wenn Sie org.springframework.boot:spring-boot-starter-actuator
und org.springframework.boot:spring-boot-starter-web
im Klassenpfad haben, stellt der Starter Folgendes bereit:
grpc
Gesundheitsindikator unter /actuator/health
endpoint.
/actuator/grpc
-Endpunkt.
Dies kann durch Standardendpunkte und Gesundheitskonfiguration gesteuert werden.
Ab Version 3.3.0
registriert der Starter den laufenden grpc-Server automatisch in der Consul-Registrierung, wenn sich org.springframework.cloud:spring-cloud-starter-consul-discovery
im Klassenpfad befindet und spring.cloud.service-registry.auto-registration.enabled
ist NICHT auf false
gesetzt.
Dem registrierten Dienstnamen wird grpc-
vorangestellt, also grpc-${spring.application.name}
um den standardmäßig registrierten Webdienstnamen nicht zu beeinträchtigen, wenn Sie sich dafür entscheiden, sowohl eingebettete Grpc
als auch Web
auszuführen.
ConsulDiscoveryProperties
werden aus Konfigurationseigenschaften mit dem Präfix „ spring.cloud.consul.discovery
“ gebunden und dann werden die Werte durch Eigenschaften mit dem Präfix grpc.consul.discovery
überschrieben (falls festgelegt). Dies ermöglicht Ihnen eine separate Consul-Erkennungskonfiguration für rest
und grpc
-Dienste, wenn Sie beide in Ihrer Anwendung verfügbar machen möchten.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
Sowohl rest
als auch grpc
-Dienste werden mit den Metadaten myKey=myValue
registriert
Restdienste werden bei myWebTag
registriert
Grpc-Dienste werden bei myGrpcTag
registriert
Wenn Sie spring.cloud.consul.discovery.register-health-check
(oder grpc.consul.discovery.register-health-check
) auf true
setzen, wird der GRPC-Integritätsprüfungsdienst bei Consul registriert.
Es gibt 4 unterstützte Registrierungsmodi:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(Standard)
In diesem Modus wird der laufende GRPC-Server als einzelner Dienst mit einzelner grpc
Prüfung und leerer serviceId
registriert.
Bitte beachten Sie, dass die Standardimplementierung nichts bewirkt und lediglich SERVING
Status zurückgibt. Möglicherweise möchten Sie für diesen Modus Ihre benutzerdefinierte Health-Check-Implementierung bereitstellen.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
In diesem Modus wird der laufende GRPC-Server als einzelner Dienst registriert, wobei jeder erkannte grpc
Dienst überprüft wird.
STANDALONE_SERVICES
In diesem Modus wird jeder erkannte grpc-Dienst als einzelner Dienst mit Einzelprüfung registriert. Jeder registrierte Dienst ist mit einem eigenen Dienstnamen gekennzeichnet.
NOOP
– keine GRPC-Dienste registriert. Dieser Modus ist nützlich, wenn Sie in Ihrer Anwendung sowohl rest
als auch grpc
-Dienste bereitstellen, aus irgendeinem Grund jedoch nur rest
bei Consul registriert werden sollten.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Wenn Sie produktionsbereite Dienste erstellen, empfiehlt es sich, ein separates Projekt für die gRPC-API Ihrer Dienste zu haben, das nur protogenerierte Klassen sowohl für die server- als auch für die clientseitige Verwendung enthält.
Anschließend fügen Sie dieses Projekt als implementation
zu Ihren gRPC client
und gRPC server
hinzu.
Um Eureka
zu integrieren, folgen Sie einfach der tollen Anleitung von Spring.
Nachfolgend sind die wesentlichen Teile der Konfigurationen für Server- und Client-Projekte aufgeführt.
Fügen Sie eureka Starter als Abhängigkeit Ihres Serverprojekts zusammen mit generierten Klassen aus proto
hinzu:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Konfigurieren Sie den gRPC-Server so, dass er sich bei Eureka registriert.
spring :
application :
name : my-service-name (1)
ServiceId
von Eureka ist standardmäßig der Name der Spring-Anwendung. Geben Sie ihn an, bevor sich der Dienst bei Eureka registriert.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
Geben Sie die Portnummer an, die der gRPC überwacht.
Registrieren Sie den Eureka-Service-Port so, dass er mit grpc.port
identisch ist, damit der Client weiß, wohin er die Anforderungen senden muss.
Geben Sie die Registrierungs-URL an, damit sich der Dienst selbst registrieren kann.
Stellen Sie den gRPC-Dienst als Teil der Spring Boot-Anwendung bereit.
@ 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 );
}
}
Fügen Sie eureka Starter als Abhängigkeit Ihres Client-Projekts zusammen mit generierten Klassen aus proto
hinzu:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Konfigurieren Sie den Client, um die Eureka-Dienstregistrierung zu finden:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
wenn dieses Projekt nicht als Dienstleistung für einen anderen Kunden dienen soll.
Geben Sie die Registrierungs-URL an, damit dieser Client weiß, wo er nach dem erforderlichen Dienst suchen muss.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
Verwenden Sie EurekaClient, um die Koordinaten der gRPC-Dienstinstanz von Eureka abzurufen und den Dienst zu nutzen:
@ 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)
}
}
Rufen Sie die Informationen zur Instanz my-service-name
ab.
Bauen Sie channel
entsprechend auf.
Erstellen Sie einen Stub mit dem channel
.
Rufen Sie den Dienst auf.
Apache 2.0