gRPC Spring Boot Starter - gRPC 框架的 Spring Boot 启动器模块


MIT
跨平台
Java

软件简介

gRPC 框架的 Spring Boot 启动器模块

特点

  • 使用@ GrpcService自动创建并运行一个 gRPC 服务,内嵌在 spring-boot 应用中

  • 使用@ GrpcClient自动创建和管理你的channelstub

  • 支持 Spring Cloud(向 ConsulEurekaNacos 注册服务并获取gRPC服务信息)

  • 支持 Spring Sleuth 进行链路跟踪(需要单独引入 brave-instrumentation-grpc)

  • 支持对 server、client 分别设置全局拦截器或单个的拦截器

  • 支持 Spring-Security

  • 支持 metric (micrometer / actuator)

  • 可以使用 (non-shaded) grpc-netty

版本

2.x.x.RELEASE 支持 Spring Boot 2.1+ & Spring Cloud Greenwich。

最新的版本:2.5.0.RELEASE

(使用 2.4.0.RELEASE 版本可以支持 Spring Boot 2.0.X & Spring Cloud Finchley).

1.x.x.RELEASE 支持 Spring Boot 1 & Spring Cloud Edgware 、Dalston、Camden。

最新的版本:1.4.2.RELEASE

注意: 此项目也可以在没有 Spring-Boot 的场景下使用,但需要手动的配置相关的 bean。

使用方式

gRPC server + client

如果使用的是 Maven,添加如下依赖

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-spring-boot-starter</artifactId>
  <version>2.5.0.RELEASE</version>
</dependency>

如果使用的 Gradle,添加如下依赖

dependencies {
  compile 'net.devh:grpc-spring-boot-starter:2.5.0.RELEASE'
}

gRPC 服务端

如果使用的是 Maven,添加如下依赖

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>2.5.0.RELEASE</version>
</dependency>

如果使用的 Gradle,添加如下依赖

dependencies {
  compile 'net.devh:grpc-server-spring-boot-starter:2.5.0.RELEASE'
}

实现 gRPC server 的业务逻辑,并使用 @GrpcService 注解

@GrpcService
public class GrpcServerService extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply.newBuilder().setMessage("Hello ==> " + req.getName()).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

设置 gRPC 的 host 跟 port ,默认的监听的 port 是 9090。其他配置属性可以参考
settings
所有的配置属性在 server 中使用需增加 grpc.server. 的前缀

服务端配置属性示例

grpc.server.port=9090
grpc.server.address=0.0.0.0
#grpc.server.inProcessName=test

对 Server 进行自定义

当前项目同样支持对 ServerBuilder 的自定义修改,需要在创建的过程中使用 GrpcServerConfigurer beans。

@Bean
public GrpcServerConfigurer keepAliveServerConfigurer() {
  return serverBuilder -> {
    if (serverBuilder instanceof NettyServerBuilder) {
      ((NettyServerBuilder) serverBuilder)
          .keepAliveTime(30, TimeUnit.SECONDS)
          .keepAliveTimeout(5, TimeUnit.SECONDS)
          .permitKeepAliveWithoutCalls(true);
    }
  };
}

Server-Security

支持使用 Spring-Security 加密你的 gRPC 应用。你只需要添加 Spring-Security(core 或者
config)依赖,然后根据需要再增加加密的配置

首先需要选择一个认证方案

  • BasicAuth(基础认证)

    @Bean
    AuthenticationManager authenticationManager() {
    final List providers = new ArrayList<>();
    providers.add(…); // Possibly DaoAuthenticationProvider
    return new ProviderManager(providers);
    }

    @Bean
    GrpcAuthenticationReader authenticationReader() {
    final List readers = new ArrayList<>();
    readers.add(new BasicGrpcAuthenticationReader());
    return new CompositeGrpcAuthenticationReader(readers);
    }

  • Bearer Authentication (OAuth2/OpenID-Connect)

    @Bean
    AuthenticationManager authenticationManager() {
    final List providers = new ArrayList<>();
    providers.add(…); // Possibly JwtAuthenticationProvider
    return new ProviderManager(providers);
    }

    @Bean
    GrpcAuthenticationReader authenticationReader() {
    final List readers = new ArrayList<>();
    readers.add(new BearerAuthenticationReader(accessToken -> new BearerTokenAuthenticationToken(accessToken)));
    return new CompositeGrpcAuthenticationReader(readers);
    }

你可能还想定义自己的 GrantedAuthoritiesConverter ,将权限和角色的信息映射到 Spring Security 的
GrantedAuthority

  • Certificate Authentication(证书认证)

    @Bean
    AuthenticationManager authenticationManager() {
    final List providers = new ArrayList<>();
    providers.add(new X509CertificateAuthenticationProvider(userDetailsService()));
    return new ProviderManager(providers);
    }

    @Bean
    GrpcAuthenticationReader authenticationReader() {
    final List readers = new ArrayList<>();
    readers.add(new SSLContextGrpcAuthenticationReader());
    return new CompositeGrpcAuthenticationReader(readers);
    }

相关的配置属性如下:

grpc.server.security.enabled=true
grpc.server.security.certificateChain=file:certificates/server.crt
grpc.server.security.privateKey=file:certificates/server.key
grpc.server.security.trustCertCollection=file:certificates/trusted-clients-collection
grpc.server.security.clientAuth=REQUIRE
  • 使用CompositeGrpcAuthenticationReader 类链式的调用多个认证方案

  • 自定义认证方式(继承并实现GrpcAuthenticationReader 类)

如何去保护你的这些服务

  • 使用 Spring-Security 的注解

    @Configuration
    @EnableGlobalMethodSecurity(proxyTargetClass = true, …)
    public class SecurityConfiguration {

如果你想使用 Spring Security 相关的注解的话,proxyTargetClass 属性是必须的! 但是你会受到一条警告,提示
MyServiceImpl#bindService() 方式是用 final 进行修饰的。 这条警告目前无法避免,但它是安全的,可以忽略它。

  • 手动配置

    @Bean
    AccessDecisionManager accessDecisionManager() {
    final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
    voters.add(new AccessPredicateVoter());
    return new UnanimousBased(voters);
    }

    @Bean
    GrpcSecurityMetadataSource grpcSecurityMetadataSource() {
    final ManualGrpcSecurityMetadataSource source = new ManualGrpcSecurityMetadataSource();
    source.set(MyServiceGrpc.getSecureMethod(), AccessPredicate.hasRole(“ROLE_USER”));
    source.setDefault(AccessPredicate.permitAll());
    return source;
    }

gRPC 客户端

如果使用的是 Maven,添加如下依赖

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-client-spring-boot-starter</artifactId>
  <version>2.5.0.RELEASE</version>
</dependency>

如果使用的 Gradle,添加如下依赖

dependencies {
  compile 'net.devh:grpc-client-spring-boot-starter:2.5.0.RELEASE'
}

这里有三种方式去或得一个gRPC server的连接

  • 使用 grpcChannelFactory.createChannel(serverName) 去创建一个 Channel,并创建一个自己的 gRPC stub.

    @Autowired
    private GrpcChannelFactory grpcChannelFactory;

    private GreeterGrpc.GreeterBlockingStub greeterStub;

    @PostConstruct
    public void init() {
    Channel channel = grpcChannelFactory.createChannel(“gRPC server name”);
    greeterStub = GreeterGrpc.newBlockingStub(channel);
    }

  • 通过在 Channel 类型的字段上加入 @GrpcClient(serverName) 注解,并创建一个自己的 gRPC stub.

    • 不需要使用 @Autowired 或者 @Inject 来进行注入

    @GrpcClient(“gRPC server name”)
    private Channel channel;

    private GreeterGrpc.GreeterBlockingStub greeterStub;

    @PostConstruct
    public void init() {
    greeterStub = GreeterGrpc.newBlockingStub(channel);
    }

  • 直接将 @GrpcClient(serverName) 注解加在调用客户端的 stub 上

    • 不需要使用 @Autowired 或者 @Inject 来进行注入

    @GrpcClient(“gRPC server name”)
    private GreeterGrpc.GreeterBlockingStub greeterStub;

注意: 你可以为多个 channels 和多个不同的 stubs 使用相同的 serverName (除非他们拦截器不一样).

然后你可以直接向服务端发起请求,如下:

HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName(name).build());

可以单独为每一个 client 配置对应的 address 但在某些情况下,你可以调整默认的配置。 你可以通过 NameResolver.Factory
beans 去自定义默认的 url 映射,如果你没有配置这个 bean,那将会按照下面的方式进行解析:

  • 如果存在一个 DiscoveryClient 的 bean,这时会使用 client name 去注册中心上进行获取对应服务的 address
  • 否则 client 端将使用 localhost9090 端口

其他的配置属性参考 settings,所有的配置文件在
client 端使用时需要增加 grpc.client.(serverName).的前缀

你也可以配置多个目标地址,请求时会自动使用负载均衡

  • static://127.0.0.1:9090,[::1]:9090

你也可以使用服务发现去获取目标地址(要求一个 DiscoveryClient bean)

  • discovery:///my-service-name

此外,你也可以使用 DNS 的方式去获取目标地址

  • dns:///example.com

同时,你也可以使用如下方式直接进程内访问

  • in-process:test

它会通过DNS将域名解析出所有真实的 IP 地址,通过使用这些真实的IP地址去做负载均衡。 需要注意的是 grpc-java 出于性能的考虑对 DNS
返回的结果做缓存。 有关这些和其他原生支持的 NameResolverProviders 参考官方文档 grpc-java
sources

客户端配置属性示例

grpc.client.GLOBAL.enableKeepAlive=true

grpc.client.(gRPC server name).address=static://localhost:9090
# Or
grpc.client.myName.address=static://localhost:9090

GLOBAL 是一个特殊的常量,它可以用于对所有 Client 统一的设置属性。 属性覆盖的顺序:Client单独的属性 > GLOBAL的属性 >
默认的属性

自定义 Client

This library also supports custom changes to the ManagedChannelBuilder and
gRPC client stubs during creation by creating GrpcChannelConfigurer and
StubTransformer beans. 当前项目支持对 ManagedChannelBuilder 的自定义,在 gRPC client
stub创建的过程中,通过使用 GrpcChannelConfigurerStubTransformer bean 来完成自定义操作

@Bean
public GrpcChannelConfigurer keepAliveClientConfigurer() {
  return (channelBuilder, name) -> {
    if (channelBuilder instanceof NettyChannelBuilder) {
      ((NettyChannelBuilder) channelBuilder)
          .keepAliveTime(15, TimeUnit.SECONDS)
          .keepAliveTimeout(5, TimeUnit.SECONDS);
    }
  };
}

@Bean
public StubTransformer authenticationStubTransformer() {
  return (clientName, stub) -> stub.withCallCredentials(grpcCredentials(clientName));
}

客户端认证

注意: 以下列出的一些方法仅仅适用于通过注入得到的 stubs,如果你通过注入 Channel,手动的在去创建 stubs,这就需要你自己手动的
去配置凭证。然而你同样能从目前所提供的一些辅助类方法中收益。

客户端有许多不同的认证方式,我们只需定义一个类型为 CallCredentials 的 bean,它会自动作用于身份验证。目前通过一些辅助方法可以支持
下列的认证方式:

  • BasicAuth

    @Bean
    CallCredentials grpcCredentials() {
    return CallCredentialsHelper.basicAuth(username, password);
    }

  • Bearer Authentication (OAuth2, OpenID-Connect)

    @Bean
    CallCredentials grpcCredentials() {
    return CallCredentialsHelper.bearerAuth(token);
    }

  • Certificate Authentication

需要一些配置属性:

#grpc.client.test.security.authorityOverride=localhost
#grpc.client.test.security.trustCertCollection=file:certificates/trusted-servers-collection
grpc.client.test.security.clientAuthEnabled=true
grpc.client.test.security.certificateChain=file:certificates/client.crt
grpc.client.test.security.privateKey=file:certificates/client.key
  • 为每个 client 使用不同的认证

通过定义一个 StubTransformer bean 来代替原有的 CallCredentials bean

@Bean
StubTransformer grpcCredentialsStubTransformer() {
  return CallCredentialsHelper.mappedCredentialsStubTransformer(
      Map.of(
          clientA, callCredentialsAC,
          clientB, callCredentialsB,
          clientC, callCredentialsAC));
}

注意: 如果你配置了 CallCredentials bean,然后再使用 StubTransformer 的话可能会造成冲突。

使用 (non-shaded)grpc-netty

当前项目目前支持 grpc-nettygrpc-netty-shaded。 使用 grpc-netty-shaded 可以防止 grpc
跟 netty 版本的兼容性问题。

注意: 如果 grpc-netty-shaded 已经存在于 classpath 中, 那么将优先使用 shaded-netty

如果你使用的Maven,你可以使用如下的配置:

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>${grpcVersion}</version>
</dependency>

<!-- For both -->
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-spring-boot-starter</artifactId>
    <version>...</version>
    <exclusions>
        <exclusion>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- For the server (only) -->
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>...</version>
    <exclusions>
        <exclusion>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- For the client (only) -->
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-starter</artifactId>
    <version>...</version>
    <exclusions>
        <exclusion>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </exclusion>
    </exclusions>
</dependency>

如果你使用的 Gradle,你可以使用如下的配置:

compile "io.grpc:grpc-netty:${grpcVersion}"

compile 'net.devh:grpc-spring-boot-starter:...' exclude group: 'io.grpc', module: 'grpc-netty-shaded' // For both
compile 'net.devh:grpc-client-spring-boot-starter:...' exclude group: 'io.grpc', module: 'grpc-netty-shaded' // For the client (only)
compile 'net.devh:grpc-server-spring-boot-starter:...' exclude group: 'io.grpc', module: 'grpc-netty-shaded' // For the server (only)