使用支持 @GRpcService 的 bean 自动配置并运行嵌入式 gRPC 服务器,作为 spring-boot 应用程序的一部分(短视频)
建议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 '
}
默认情况下,如果您被迫使用纯grpc-netty
依赖项,starter 会拉取io.grpc:grpc-netty-shaded
作为传递依赖项:
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 依赖管理插件,它可能会提取与编译启动的版本不同的版本,从而导致二进制不兼容问题。
在这种情况下,您需要强制且显式地设置要使用的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 来定义随机端口。然后可以通过在 int 字段上使用@LocalRunningGrpcPort 注释来检索正在使用的实际端口,该注释将注入正在运行的端口(显式配置或随机选择) |
可选择启用服务器反射(请参阅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
)
也通过预定义端口6767
暴露在内部网络 IP 上。
如果依赖项中同时有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 ();
}
}
starter支持两种拦截器的注册: Global和Per Service 。
在这两种情况下,拦截器都必须实现io.grpc.ServerInterceptor
接口。
每项服务
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
如果存在LogInterceptor
类型的 bean,则LogInterceptor
将通过 spring 工厂实例化,否则通过无参数构造函数实例化。
全球的
@ 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
属性公开了其他侦听地址)
配置您选择的导出器后,您应该看到名为grpc.server.calls
的timer
。
通过在应用程序上下文中定义GRpcMetricsTagsContributor
bean,您可以将自定义标签添加到grpc.server.calls
计时器。
您还可以使用RequestAwareGRpcMetricsTagsContributor
bean 来标记一元和流调用。
演示在这里
保持较低的离散度,以免夸大度量的基数。 |
如果metric
拦截器的优先级高于security
拦截器并且grpc.security.auth.fail-fast
设置为false
,则RequestAwareGRpcMetricsTagsContributor
仍可以在身份验证失败时执行。
本测试涵盖了这种情况。
请务必阅读拦截器订购章节。 |
确保包含以下依赖项:
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 服务消息。请继续实施消息验证以获取配置详细信息。
启动器内部定义了java.util.function.Consumer
类型的 bean,当spring-cloud-stream
位于类路径上时,该 bean 被考虑用于函数注册,这是不可取的(如果您恰好有, spring-cloud-stream
会自动注册通道)应用程序上下文中的一个 Consumer/Supplier/Function bean,因此如果您将此启动器与spring-cloud-stream
一起使用,那么您已经拥有了一个。
据此,建议在生产就绪应用程序中使用spring.cloud.function.definition
属性,而不是依赖自动发现。
请参考GRPC Kafka Stream demo,最核心的部分就是这一行。
Starter 为利用与 Spring Security 框架的集成对用户进行身份验证和授权提供了内置支持。
有关支持的身份验证提供程序和配置选项的详细信息,请参阅 Spring Security 集成部分。
可以使用根证书及其私钥路径来配置传输安全性:
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.Server
io.grpc.ServerBuilder
实例,您可以将从org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
继承的 bean 添加到您的上下文并覆盖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 集成了 SalesForce 的reactive-grpc protoc 插件:
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
,它负责将服务抛出的异常传播到错误处理程序。
可以通过使用@GRpcServiceAdvice
注释的 bean 以及使用@GRpcExceptionHandler
注释注释的方法来注册错误处理方法。
这些被视为global
错误处理程序,并且调用类型层次结构中最接近所引发异常的异常类型参数的方法。
错误处理程序的签名必须遵循以下模式:
返回类型 | 参数1 | 参数2 |
---|---|---|
io.grpc.状态 | 任何 | GRpc异常范围 |
@ GRpcServiceAdvice
class MyHandler1 {
@ GRpcExceptionHandler
public Status handle ( MyCustomExcpetion exc , GRpcExceptionScope scope ){
}
@ GRpcExceptionHandler
public Status handle ( IllegalArgumentException exc , GRpcExceptionScope scope ){
}
}
@ GRpcServiceAdvice
class MyHandler2 {
@ GRpcExceptionHandler
public Status anotherHandler ( NullPointerException npe , GRpcExceptionScope scope ){
}
}
您可以拥有任意数量的advice
bean 和处理程序方法,只要它们不会相互干扰并且不会创建已处理异常类型的歧义。
grpc
服务 bean 也被发现用于错误处理程序,其优先级高于@GRpcServiceAdvice
bean 中发现的全局错误处理方法。服务级别的错误处理方法被认为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
}
}
由于grpc
服务 API 的性质不允许抛出检查异常,因此提供了特殊的运行时异常类型来包装检查异常。然后在查找处理程序方法时将其解开。
当抛出GRpcRuntimeExceptionWrapper
异常时,您还可以传递hint
对象,然后可以从handler
方法中的scope
对象访问该提示对象。
运行时异常可以按原样抛出,不需要包装。
获取提示对象。
将自定义标头发送给客户端。
身份验证失败通过AuthenticationException
传播,授权失败通过AccessDeniedException
传播。
验证失败通过ConstraintViolationException
传播:对于失败的请求 - 以Status.INVALID_ARGUMENT
作为提示,对于失败的响应 - 以Status.FAILED_PRECONDITION
作为提示。
演示在这里
由于通过 XML 部署描述符提供了 Bean Validation 配置支持,因此可以通过 XML 为生成的类提供约束,而不是使用自定义protoc
编译器来检测生成的消息。
将org.springframework.boot:spring-boot-starter-validation
依赖项添加到您的项目中。
创建META-INF/validation.xml
和约束声明文件。 (IntelliJ IDEA 对授权 bean 验证约束 xml 文件有很好的自动完成支持)
另请参阅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
响应状态。演示在这里
虽然仍然可以使用@Transactional
注解你的 rpc 方法(如果默认情况下未启用,则使用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 安全配置遵循与 Spring WEB 安全配置相同的原则和 API,如果类路径中有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
注释结合起来。
此默认配置保护使用org.springframework.security.access.annotation.@Secured
注释注释的 GRPC 方法/服务。
将注释的值保留为空( @Secured({})
)意味着:仅authenticate
,不会执行授权。
如果您的上下文中存在JwtDecoder
bean,它还将注册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 )
}
}
获取授权配置对象
具有SCOPE_profile
权限的经过身份验证的用户允许sayHello
方法的MethodDefinition
。
使用JwtAuthenticationProvider
针对使用spring.security.oauth2.resourceserver.jwt.issuer-uri
属性配置的资源服务器验证用户声明( BEARER
令牌)。
可以通过实现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
对象作为声明(尚未经过身份验证)
注册您自己的支持MyAuthenticationObject
验证的AuthenticationProvider
验证提供的authentication
并返回经过验证和身份验证的Authentication
对象
还可以通过在应用程序上下文中定义 Spring bean 来注册AuthenticationSchemeSelector
:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
客户端配置支持部分解释了如何从 GRPC 客户端传递自定义授权方案和声明。
从版本4.5.9
开始,您还可以在 grpc 服务方法和 grpc 服务类型上使用标准@PreAuthorize
和@PostAuthorize
注释。
呼叫类型 | 输入对象引用 | 输出对象引用 | 样本 |
---|---|---|---|
一元 | 按参数名称 |
| @ 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
时,身份验证标头将使用 ASCII 编组器与Authorization
密钥一起发送。
提供token生成器功能(请参考示例)
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 的默认实现。
您可以通过在应用程序上下文中注册 ManagedHealthStatusService bean 来提供自己的服务。
如果类路径中有org.springframework.boot:spring-boot-starter-actuator
和org.springframework.boot:spring-boot-starter-web
,启动器将公开:
/actuator/health
端点下的grpc
健康状况指示器。
/actuator/grpc
端点。
这可以通过标准端点和运行状况配置来控制。
从版本3.3.0
开始,如果org.springframework.cloud:spring-cloud-starter-consul-discovery
位于类路径中并且spring.cloud.service-registry.auto-registration.enabled
starter 将在 Consul 注册表中自动注册正在运行的 grpc 服务器spring.cloud.service-registry.auto-registration.enabled
未设置为false
。
如果您选择同时运行嵌入式Grpc
和Web
服务器,注册的服务名称将以grpc-
为前缀,即grpc-${spring.application.name}
以免干扰标准注册的 Web 服务名称。
ConsulDiscoveryProperties
从以spring.cloud.consul.discovery
为前缀的配置属性绑定,然后由grpc.consul.discovery
前缀的属性(如果设置)覆盖这些值。如果您选择从应用程序中公开这两个服务,这允许您为rest
和grpc
服务拥有单独的consul发现配置。
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
rest
和grpc
服务都使用元数据myKey=myValue
进行注册
Rest 服务已注册到myWebTag
Grpc 服务注册到myGrpcTag
将spring.cloud.consul.discovery.register-health-check
(或grpc.consul.discovery.register-health-check
)设置为true
将向 Consul 注册 GRPC 健康检查服务。
支持4种注册模式:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(默认)
在此模式下,正在运行的 grpc 服务器注册为单个服务,并使用空serviceId
进行单个grpc
检查。
请注意,默认实现不执行任何操作,只是返回SERVING
状态。您可能希望为此模式提供自定义运行状况检查实现。
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
在此模式下,正在运行的 grpc 服务器被注册为单个服务,并根据每个发现的grpc
服务进行检查。
STANDALONE_SERVICES
在此模式下,每个发现的 grpc 服务都注册为单个服务并进行单次检查。每个注册的服务都由其自己的服务名称标记。
NOOP
- 没有注册 grpc 服务。如果您在应用程序中同时提供rest
和grpc
服务,但由于某种原因,只有rest
服务应该向 Consul 注册,则此模式非常有用。
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)
Eureka的ServiceId
默认是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 从 Eureka 获取 gRPC 服务实例的坐标并使用该服务:
@ 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