Автоматически настраивает и запускает встроенный сервер gRPC с компонентами с поддержкой @GRpcService как часть приложения весенней загрузки (короткое видео)
Пользователям Gradle
рекомендуется применить плагин:
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
Плагин io.github.lognet.grpc-spring-boot gradle значительно упрощает настройку проекта.
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 '
}
По умолчанию стартер использует io.grpc:grpc-netty-shaded
как транзитивную зависимость, если вы вынуждены использовать чистую зависимость grpc-netty
:
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)
Обязательно скачайте версию, соответствующую выпуску.
Присутствие обеих библиотек в пути к классам также поддерживается свойством grpc.netty-server.on-collision-prefer-shaded-netty
.
Если вы используете плагин Spring Boot Dependency Management, он может получить не ту версию, с которой была скомпилирована запущенная версия, что приведет к проблеме двоичной несовместимости.
В этом случае вам нужно будет принудительно и явно установить используемую версию grpc
(см. матрицу версий здесь):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
Примечания к выпуску с матрицей совместимости можно найти здесь. |
Следуйте этому руководству, чтобы создать заглушку и серверный интерфейс из ваших файлов .proto
.
Если у вас стек с maven - используйте эту ссылку.
Аннотируйте реализацию интерфейса вашего сервера с помощью @org.lognet.springboot.grpc.GRpcService
При необходимости настройте порт сервера в файле application.yml/properties
. Порт по умолчанию — 6565
.
grpc :
port : 6565
Случайный порт можно определить, установив для него значение 0 .Фактический используемый порт затем можно получить с помощью аннотации @LocalRunningGrpcPort в поле int , которая будет вводить работающий порт (явно настроенный или случайно выбранный). |
При необходимости включите отражение сервера (см. https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md).
grpc :
enableReflection : true
При необходимости установите порядок фаз запуска (по умолчанию Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
При необходимости установите количество секунд ожидания завершения ранее существовавших вызовов во время корректного завершения работы сервера. В это время новые вызовы будут отклонены. Отрицательное значение эквивалентно бесконечному льготному периоду. Значение по умолчанию — 0
(означает «не ждать»).
grpc :
shutdownGrace : 30
Свойства сервера, специфичные для Netty, можно указать с помощью префикса grpc.netty-server
.
Настраивая одно из значений grpc.netty-server.xxxx
вы неявно устанавливаете транспорт на основе 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)
Свойства типа Duration
можно настроить с использованием формата строкового значения, описанного здесь.
Свойства типа DataSize
можно настроить с помощью строкового значения, описанного здесь.
Доступен по IP-адресу внешней сети с настраиваемым портом.
Формат строкового значения свойств типа SocketAddress
:
host:port
(если значение port
меньше 1, используется случайное значение)
host:
(использует порт grpc по умолчанию, 6565
)
Доступен также по IP-адресу внутренней сети с предопределенным портом 6767
.
Если в зависимостях имеются как shaded
, так и pure
библиотеки Netty, выберите тип NettyServerBuilder
, который необходимо создать. Это тип, который будет передан в GRpcServerBuilderConfigurer
(см. Пользовательскую конфигурацию сервера gRPC), по умолчанию имеет значение true
(т. е. io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
, если false
).
Стартер также поддерживает in-process server
, который следует использовать в целях тестирования:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
Отключает сервер по умолчанию ( NettyServer
).
Включает in-process
сервер.
Если вы включите и NettyServer , и in-process сервер, они оба будут использовать один и тот же экземпляр HealthStatusManager и GRpcServerBuilderConfigurer (см. Пользовательскую конфигурацию сервера gRPC). |
В проекте grpc-spring-boot-starter-demo
вы можете найти полнофункциональные примеры с интеграционными тестами.
Определение сервиса из файла .proto
выглядит следующим образом:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
Обратите внимание на сгенерированный класс io.grpc.examples.GreeterGrpc.GreeterImplBase
, который расширяет io.grpc.BindableService
.
Все, что вам нужно сделать, это аннотировать реализацию вашего сервиса с помощью @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 ();
}
}
Стартер поддерживает регистрацию двух видов перехватчиков: Global и Per Service .
В обоих случаях перехватчик должен реализовать интерфейс io.grpc.ServerInterceptor
.
За услугу
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
LogInterceptor
будет создан с помощью фабрики Spring, если существует компонент типа LogInterceptor
, или в противном случае с помощью конструктора без аргументов.
Глобальный
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
Также поддерживается аннотация к фабричному методу конфигурации 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 );
}
};
}
}
Конкретная служба также имеет возможность отключить глобальные перехватчики:
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Глобальные перехватчики можно упорядочить с помощью аннотаций Spring @Ordered
или @Priority
. Следуя семантике упорядочивания Spring, значения более низкого порядка имеют более высокий приоритет и будут выполняться первыми в цепочке перехватчиков.
@ 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
}
Стартер использует встроенные перехватчики для реализации обработки ошибок, интеграции Spring Security
, Validation
и Metrics
. Их порядок также можно контролировать с помощью следующих свойств:
grpc.recovery.interceptor-order
(порядок обработки ошибок, по умолчанию Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
(по умолчанию Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
(по умолчанию Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
(по умолчанию Ordered.HIGHEST_PRECEDENCE+20
)
Это дает вам возможность установить желаемый порядок встроенных и пользовательских перехватчиков.
Продолжайте читать!!! Есть еще
Принцип работы перехватчика grpc заключается в том, что он перехватывает вызов и возвращает прослушиватель вызовов сервера, который, в свою очередь, также может перехватить сообщение запроса, прежде чем перенаправить его фактическому обработчику вызова службы:
Если для свойства grpc.security.auth.fail-fast
установлено значение false
все нисходящие перехватчики, а также все восходящие перехватчики (On_Message) все равно будут выполняться в случае сбоя аутентификации/авторизации.
Предполагая, что interceptor_2
— это securityInterceptor
:
В случае неудачной аутентификации/авторизации с помощью grpc.security.auth.fail-fast=true
(по умолчанию):
В случае неудачной аутентификации/авторизации с помощью grpc.security.auth.fail-fast=false
:
Этот запуск изначально поддерживается проектом spring-cloud-sleuth
.
Пожалуйста, продолжайте следить за интеграцией grpc.
Включив зависимость org.springframework.boot:spring-boot-starter-actuator
, стартер будет собирать метрики сервера gRPC с разбивкой по
method
— метод службы gRPC FQN (полное имя)
result
- код состояния ответа
address
- локальный адрес сервера (если вы предоставили дополнительные адреса прослушивания, со свойством grpc.netty-server.additional-listen-addresses
)
После настройки экспортера по вашему выбору вы должны увидеть timer
с именем grpc.server.calls
.
Определив bean-компонент GRpcMetricsTagsContributor
в контексте вашего приложения, вы можете добавлять собственные теги к таймеру grpc.server.calls
.
Вы также можете использовать bean-компонент RequestAwareGRpcMetricsTagsContributor
для маркировки унарных и потоковых вызовов.
Демо здесь
Держите дисперсию низкой, чтобы не увеличить мощность метрики. |
RequestAwareGRpcMetricsTagsContributor
по-прежнему может выполняться в случае неудачной аутентификации, если перехватчик metric
имеет более высокий приоритет, чем перехватчик security
, а grpc.security.auth.fail-fast
установлено значение false
.
Этот случай рассматривается в данном тесте.
Обязательно прочтите главу заказа перехватчиков. |
Обязательно включите следующие зависимости:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
Конфигурация:
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
Стандартные конечные точки /actuator/metrics
и /actuator/prometheus
будут отображать метрики grpc.server.calls
(см. демонстрацию здесь).
Предложение об отказе от GRPC |
Стартер можно автоматически настроить для проверки сообщений службы gRPC запроса/ответа. Пожалуйста, перейдите к разделу «Реализация проверки сообщений» для получения подробной информации о конфигурации.
Стартер внутренне определяет bean-компонент типа java.util.function.Consumer
, который рассматривается для реестра функций, когда spring-cloud-stream
находится в пути к классам, что нежелательно ( spring-cloud-stream
автоматически регистрирует канал, если у вас есть точно один bean-компонент Consumer/Supplier/Function в контексте приложения, поэтому он у вас уже есть, если вы используете этот стартер вместе с spring-cloud-stream
).
В соответствии с этим рекомендуется использовать свойство spring.cloud.function.definition
в готовых к производству приложениях и не полагаться на автоматическое обнаружение.
Пожалуйста, обратитесь к демо-версии GRPC Kafka Stream, основная часть — эта строка.
Стартер обеспечивает встроенную поддержку аутентификации и авторизации пользователей с использованием интеграции с платформой Spring Security.
Пожалуйста, обратитесь к разделам Spring Security Integration для получения подробной информации о поддерживаемых поставщиках аутентификации и параметрах конфигурации.
Транспортную безопасность можно настроить с помощью корневого сертификата вместе с путем к его личному ключу:
grpc :
security :
cert-chain : classpath:cert/server-cert.pem
private-key : file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
Значение обоих свойств имеет форму, поддерживаемую ResourceEditor.
Клиентская часть должна быть настроена соответствующим образом:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
Этот стартер по умолчанию будет использовать зависимость io.netty:netty-tcnative-boringssl-static
для поддержки SSL.
Если вам нужна другая поддержка SSL/TLS, исключите эту зависимость и следуйте Руководству по безопасности.
Если для настройки безопасности необходима более детальная настройка, используйте собственный конфигуратор, описанный в разделе Пользовательская конфигурация сервера gRPC. |
Чтобы перехватить экземпляр io.grpc.ServerBuilder
, используемый для сборки io.grpc.Server
, вы можете добавить компонент, наследуемый от org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
в свой контекст и переопределить метод configure
.
Также поддерживаются несколько конфигураторов.
К моменту вызова метода configure
все обнаруженные сервисы, включая их перехватчики, были добавлены в переданный сборщик.
В вашей реализации метода configure
вы можете добавить свою собственную конфигурацию:
@ 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 ) ;
}
};
}
Если вы включите как NettyServer , так и in-process серверы, метод configure будет вызываться в одном и том же экземпляре конфигуратора.Если вам нужно различать переданные serverBuilder , вы можете проверить тип.Это текущее ограничение. |
GRpcServerInitializedEvent
публикуется при запуске сервера, вы можете использовать его с помощью обычного Spring API.
Начиная с версии 5.1.0
, Spring-boot-starter-gradle-plugin интегрирует плагин протокола Reactive-grpc SalesForce:
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
Вот тесты и пример службы реактивного grpc.
Стартер регистрирует GRpcExceptionHandlerInterceptor
, который отвечает за распространение исключения, созданного службой, обработчикам ошибок.
Метод обработки ошибок можно зарегистрировать с помощью аннотированного bean-компонента @GRpcServiceAdvice
с методами, аннотированными аннотациями @GRpcExceptionHandler
.
Они рассматриваются как global
обработчики ошибок, и вызывается метод с параметром типа исключения, ближайшим по иерархии типов к выброшенному исключению.
Подпись обработчика ошибок должна соответствовать следующему шаблону:
Тип возврата | Параметр 1 | Параметр 2 |
---|---|---|
io.grpc.Status | любой тип | 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 ){
}
}
Вы можете иметь столько компонентов advice
и методов-обработчиков, сколько захотите, при условии, что они не мешают друг другу и не создают неоднозначность типа обрабатываемого исключения.
Служебный компонент grpc
также обнаруживается для обработчиков ошибок и имеет более высокий приоритет, чем глобальные методы обработки ошибок, обнаруженные в bean-компонентах @GRpcServiceAdvice
. Методы обработки ошибок на уровне службы считаются private
и вызываются только тогда, когда эта служба генерирует исключение:
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
}
}
Поскольку природа API службы grpc
не позволяет создавать проверенное исключение, для переноса проверенного исключения предоставляется специальный тип исключения времени выполнения. Затем он разворачивается при поиске метода обработчика.
При создании исключения GRpcRuntimeExceptionWrapper
вы также можете передать объект hint
, который затем будет доступен из объекта scope
в методе handler
.
Исключение времени выполнения может быть выброшено как есть, и его не нужно оборачивать.
Получите объект подсказки.
Отправьте клиенту пользовательские заголовки.
Ошибка аутентификации распространяется через AuthenticationException
, а ошибка авторизации — через AccessDeniedException
.
Ошибка проверки распространяется через ConstraintViolationException
: для неудавшегося запроса — с подсказкой Status.INVALID_ARGUMENT
, а для неудавшегося ответа — с подсказкой Status.FAILED_PRECONDITION
.
Демо здесь
Благодаря поддержке конфигурации проверки компонентов через дескриптор развертывания XML можно предоставить ограничения для сгенерированных классов через XML вместо инструментирования сгенерированных сообщений с помощью специального компилятора protoc
.
Добавьте зависимость org.springframework.boot:spring-boot-starter-validation
в свой проект.
Создайте файл(ы) META-INF/validation.xml
и объявления ограничений. (IntelliJ IDEA имеет отличную поддержку автозаполнения для авторизации XML-файлов ограничений проверки bean-компонентов)
См. также примеры из документации валидатора Hibernate
.
Демо-конфигурацию и соответствующие тесты можно найти здесь.
Обратите внимание, что проверяются как сообщения request
, так и response
.
Если ваш метод gRPC использует один и тот же тип сообщения запроса и ответа, вы можете использовать группы проверки org.lognet.springboot.grpc.validation.group.RequestMessage
и org.lognet.springboot.grpc.validation.group.ResponseMessage
для применения различной логики проверки. :
...
< 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 >
...
Примените это ограничение только для сообщения request
.
Примените это ограничение только для response
сообщения.
Обратите также внимание на пользовательское ограничение между полями и его использование:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
Как описано в главе «Упорядочение перехватчиков», вы можете присвоить перехватчику validation
более высокий приоритет, чем перехватчику security
, и установить для свойства grpc.security.auth.fail-fast
значение false
.
В этом сценарии, если вызов не прошел проверку подлинности и недействителен, клиент получит статус ответа Status.INVALID_ARGUMENT
вместо статуса ответа Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. Демо здесь
Хотя все еще возможно аннотировать ваши методы rpc с помощью @Transactional
(с помощью spring.aop.proxy-target-class=true
, если он не включен по умолчанию), есть вероятность, что вы получите непредсказуемое поведение. Рассмотрим ниже реализацию метода 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)
}
Метод аннотирован как @Transactional
. Spring зафиксирует транзакцию через некоторое время после возврата методов.
Ответ возвращается вызывающему абоненту
Методы возвращаются, транзакция в конечном итоге фиксируется.
Теоретически и, как вы можете видеть, практически, существует небольшой промежуток времени, когда клиент (если задержка в сети минимальна и ваш сервер grpc поддерживает переключение контекста сразу после <2>) может попытаться получить доступ к базе данных через другой вызов grpc перед транзакция зафиксирована.
Решением этой ситуации является вынесение логики транзакций в отдельный класс обслуживания:
@ 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 ();
}
}
Метод обслуживания транзакционный
В конечном итоге транзакция фиксируется.
Ответ после подтверждения транзакции.
Следуя этому подходу, вы также отделяете транспортный уровень и бизнес-логику, которую теперь можно тестировать отдельно.
Схема | Зависимости |
---|---|
Базовый |
|
Носитель |
|
Обычай |
|
Конфигурация безопасности GRPC соответствует тем же принципам и API, что и конфигурация безопасности Spring WEB. Она включена по умолчанию, если в вашем пути к классам есть зависимость org.springframework.security:spring-security-config
.
Вы можете использовать аннотацию @Secured
для служб/методов для защиты ваших конечных точек или использовать API и переопределить значения по умолчанию (которые предшествуют аннотации @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 )
}
}
или объедините API
с аннотациями @Secured
.
Эта конфигурация по умолчанию защищает методы/сервисы GRPC, помеченные аннотацией org.springframework.security.access.annotation.@Secured
.
Если оставить значение аннотации пустым ( @Secured({})
), это означает: только authenticate
, авторизация выполняться не будет.
Если компонент JwtDecoder
существует в вашем контексте, он также зарегистрирует JwtAuthenticationProvider
для обработки проверки утверждения аутентификации.
BasicAuthSchemeSelector
и BearerTokenAuthSchemeSelector
также автоматически регистрируются для поддержки аутентификации с использованием имени пользователя, пароля и токена носителя.
Установив для grpc.security.auth.enabled
значение false
, безопасность GRPC можно отключить.
Настройка конфигурации безопасности GRPC осуществляется путем расширения GrpcSecurityConfigurerAdapter
(различные примеры конфигурации и тестовые сценарии можно найти здесь).
@ 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 )
}
}
Получить объект конфигурации авторизации
MethodDefinition
метода sayHello
разрешено для аутентифицированных пользователей с полномочиями SCOPE_profile
.
Используйте JwtAuthenticationProvider
для проверки утверждения пользователя (токена BEARER
) на сервере ресурсов, настроенном со свойством spring.security.oauth2.resourceserver.jwt.issuer-uri
.
Можно подключить собственный поставщик аутентификации, реализовав интерфейс 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 );
}
});
}
}
Защитите все методы служб.
Зарегистрируйте свой собственный AuthenticationSchemeSelector
.
На основе предоставленного заголовка авторизации — вернуть объект Authentication
в качестве утверждения (еще не аутентифицированного)
Зарегистрируйте свой собственный AuthenticationProvider
, который поддерживает проверку MyAuthenticationObject
Проверить предоставленную authentication
и вернуть проверенный и аутентифицированный объект Authentication
AuthenticationSchemeSelector
также можно зарегистрировать, определив bean-компонент Spring в контексте вашего приложения:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
В разделе поддержки конфигурации на стороне клиента объясняется, как передать пользовательскую схему авторизации и запрос от клиента GRPC.
Начиная с версии 4.5.9
вы также можете использовать стандартные аннотации @PreAuthorize
и @PostAuthorize
для методов службы grpc и типов служб grpc.
Тип вызова | Ссылка на входной объект | Ссылка на выходной объект | Образец |
---|---|---|---|
Унарный | По имени параметра | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Входной поток, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
Одиночный запрос, | По имени параметра | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Ручей Биди | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
Чтобы получить объект Authentication
в реализации защищенного метода , используйте приведенный ниже фрагмент.
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
Начиная с 4.5.6
объект Authentication
также можно получить через стандартный Spring API:
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
Добавив зависимость io.github.lognet:grpc-client-spring-boot-starter
к вашему клиентскому приложению Java grpc, вы можете легко настроить учетные данные для каждого канала или для каждого вызова:
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
}
}
Создать перехватчик клиента
Канал перехвата
Включите/выключите двоичный формат:
Если true
, заголовок аутентификации отправляется с ключом Authorization-bin
с использованием двоичного маршаллера.
Если false
, заголовок аутентификации отправляется с ключом Authorization
с использованием маршаллера ASCII.
Обеспечить функцию генератора токенов (см., например.)
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 ();
}
}
Создание учетных данных для вызова с помощью базовой схемы
Создать заглушку службы
Прикрепите учетные данные звонка к звонку
AuthHeader
также может быть построен с использованием специальной схемы авторизации:
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
Стартер регистрирует реализацию HealthServiceImpl по умолчанию.
Вы можете предоставить свои собственные, зарегистрировав bean-компонент ManagedHealthStatusService в контексте вашего приложения.
Если у вас есть org.springframework.boot:spring-boot-starter-actuator
и org.springframework.boot:spring-boot-starter-web
в пути к классам, стартер предоставит:
Индикатор работоспособности grpc
в конечной точке /actuator/health
.
Конечная точка /actuator/grpc
.
Это можно контролировать с помощью стандартных конечных точек и конфигурации работоспособности.
Начиная с версии 3.3.0
, стартер автоматически зарегистрирует работающий сервер grpc в реестре Consul, если org.springframework.cloud:spring-cloud-starter-consul-discovery
находится в пути к классам и spring.cloud.service-registry.auto-registration.enabled
НЕ имеет значения false
.
Имя зарегистрированной службы будет иметь префикс grpc-
, т.е. grpc-${spring.application.name}
чтобы не мешать стандартному зарегистрированному имени веб-службы, если вы решите запускать как встроенный Grpc
, так и Web
серверы.
ConsulDiscoveryProperties
привязываются к свойствам конфигурации с префиксом spring.cloud.consul.discovery
, а затем значения перезаписываются свойствами с префиксом grpc.consul.discovery
(если они установлены). Это позволяет вам иметь отдельную конфигурацию обнаружения консула для служб rest
и grpc
, если вы решите предоставить доступ к обеим службам из своего приложения.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
Службы rest
и grpc
регистрируются с помощью метаданных myKey=myValue
Службы отдыха зарегистрированы в myWebTag
Службы Grpc зарегистрированы с помощью myGrpcTag
Если для параметра spring.cloud.consul.discovery.register-health-check
(или grpc.consul.discovery.register-health-check
) установлено значение true
служба проверки работоспособности GRPC будет зарегистрирована в Consul.
Поддерживаются 4 режима регистрации:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(по умолчанию)
В этом режиме работающий сервер grpc регистрируется как единая служба с одной проверкой grpc
с пустым serviceId
.
Обратите внимание, что реализация по умолчанию ничего не делает и просто возвращает статус SERVING
. Возможно, вы захотите предоставить собственную реализацию проверки работоспособности для этого режима.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
В этом режиме работающий grpc-сервер регистрируется как единый сервис с проверкой каждого обнаруженного grpc
сервиса.
STANDALONE_SERVICES
В этом режиме каждый обнаруженный сервис grpc регистрируется как отдельный сервис с однократной проверкой. Каждая зарегистрированная служба помечается собственным именем службы.
NOOP
- службы grpc не зарегистрированы. Этот режим полезен, если вы обслуживаете в своем приложении как сервисы rest
, так и grpc
, но по какой-то причине в Consul должны быть зарегистрированы только сервисы rest
.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
При создании готовых к использованию сервисов рекомендуется иметь отдельный проект для gRPC API ваших сервисов, который содержит только созданные прототипы классы как для использования на стороне сервера, так и на стороне клиента.
Затем вы добавите этот проект в качестве зависимости implementation
в проекты gRPC client
и gRPC server
.
Чтобы интегрировать Eureka
просто следуйте замечательному руководству Spring.
Ниже приведены основные части конфигураций как серверных, так и клиентских проектов.
Добавьте eureka starter в качестве зависимости вашего серверного проекта вместе со сгенерированными классами из файлов proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Настройте сервер gRPC для регистрации в Eureka.
spring :
application :
name : my-service-name (1)
ServiceId
Eureka по умолчанию — это имя приложения Spring, укажите его до того, как служба зарегистрируется в Eureka.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
Укажите номер порта, который прослушивает gRPC.
Зарегистрируйте порт службы eureka, чтобы он был таким же, как grpc.port
, чтобы клиент знал, куда отправлять запросы.
Укажите URL-адрес реестра, в котором служба зарегистрируется.
Предоставьте службу gRPC как часть приложения 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 );
}
}
Добавьте eureka starter в качестве зависимости вашего клиентского проекта вместе со сгенерированными классами из файлов proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Настройте клиент для поиска реестра службы eureka:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
, если этот проект не предназначен для предоставления услуги другому клиенту.
Укажите URL-адрес реестра, чтобы этот клиент знал, где найти необходимую службу.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
Используйте EurekaClient, чтобы получить координаты экземпляра службы gRPC от Eureka и использовать эту службу:
@ 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)
}
}
Получите информацию об экземпляре my-service-name
.
Постройте channel
соответствующим образом.
Создайте заглушку, используя channel
.
Вызовите службу.
Апач 2.0