Nestjs框架: gRPC微服务通信及安全实践全解析

gRPC基础通信实现与Proto转TS方案

原理与配置

gRPC作为现代开源高性能RPC框架,通过Protobuf配置文件生成调用代码,实现跨语言服务通信。

其分布式特性与高性能优势使其成为微服务通信的核心方案。

核心流程分为三个步骤:

1 ) 依赖安装与配置

bash 复制代码
安装NestJS的gRPC传输器和类型支持 
pnpm add @nestjs/microservices @grpc/grpc-js @grpc/proto-loader 

2 ) Proto文件定义(hero.proto)

文件规范:字段标识符从1开始递增,避免使用19000--19999预留值。

常用类型:int32floatstringboolbytes

protobuf 复制代码
syntax = "proto3";
 
package hero;
service HeroService {
  rpc FindOne (HeroById) returns (Hero) {}
}
 
message HeroById {
  int32 id = 1;
}
 
message Hero {
  int32 id = 1;
  string name = 2;
}

NestJS编译配置(nest-cli.json):

json 复制代码
{
  "compilerOptions": {
    "assets": ["/*.proto"],
    "watchAssets": true 
  }
}

3 ) 服务端启动配置(main.ts)

typescript 复制代码
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
    transport: Transport.GRPC,
    options: {
      package: 'hero',
      protoPath: join(__dirname, '../proto/hero.proto'),
      url: '0.0.0.0:50000'
    },
  });
  await app.listen();
}
bootstrap();

typescript 复制代码
// 示例:NestJS gRPC服务端配置 
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { HeroById, Hero } from './interfaces/hero.interface';
 
@Controller()
export class HeroController {
  // 模拟
  @GrpcMethod('HeroService', 'FindOne')
  findOne(data: HeroById): Hero {
    const items: Hero[] = [{ id: 1, name: 'John' }, { id: 2, name: 'Doe' }];
    return items.find(({ id }) => id === data.id);
  }
}

4 )客户端配置

typescript 复制代码
// 示例:NestJS gRPC客户端配置 
import { Module } from '@nestjs/common';
import { ClientGrpcProxy, ClientModule } from '@nestjs/microservices';
import { join } from 'path';
import { HeroService } from './interfaces/hero.interface';
 
@Module({
  imports: [
    ClientModule.register([
      {
        name: 'HERO_PACKAGE',
        transport: Transport.GRPC,
        options: {
          package: 'hero',
          protoPath: join(__dirname, '../proto/hero.proto'),
        },
      },
    ]),
  ],
  providers: [{
    provide: 'HERO_SERVICE',
    useFactory: (client: ClientGrpcProxy) => client.getService<HeroService>('HeroService'),
    inject: ['HERO_PACKAGE'],
  }],
})
export class AppModule {}

客户端调用

ts 复制代码
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { HeroService } from './hero.interface';

@Injectable()
export class ClientService {
  private heroService: HeroService;

  constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {}

  onModuleInit() {
    this.heroService = this.client.getService<HeroService>('HeroService');
  }

  async getHero(): Promise<Hero> {
    return this.heroService.findOne({ id: 1 }).toPromise();
  }
}

5 )类型生成方案对比

方案 实现方式 适用场景 优势
VSCode插件 选择.proto文件 → 执行PTS转换 快速原型开发 即时生成,无需命令行
ts-proto 命令行生成类型定义 生产环境 完整类型和方法支持

方案一:VSCode插件实时转换

  1. 安装vscode-proto3插件

  2. 全选.proto文件内容

  3. 执行Proto > Compile Selection to Typescript

  4. 输出示例:

    typescript 复制代码
    export interface Hero {
      id: number;
      name: string;
    }
    export interface HeroService {
      FindOne(request: HeroById): Promise<Hero>;
    }

方案二:ts-proto工具链生成

bash 复制代码
全局安装工具 
npm install -g ts-proto 
 
生成类型定义
protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=./generated \
--ts_proto_opt=outputServices=grpc-js \
proto/hero.proto 
  • 优势:生成完整序列化/反序列化方法,支持复杂类型和流处理
  • 输出内容:包含hero.ts(消息类型)和hero.grpc.ts(服务客户端/服务端桩代码)

安全通信核心代码

核心步骤:通过CA证书链验证服务端与客户端身份,防止中间人攻击

typescript 复制代码
// 服务端SSL配置(NestJS)
import { readFileSync } from 'fs';
import { join } from 'path';
import { ServerCredentials } from '@grpc/grpc-js';
 
const serverCredentials = ServerCredentials.createSsl(
  readFileSync(join(__dirname, '../certs/ca.pem')), // CA根证书 
  [{
    cert_chain: readFileSync(join(__dirname, '../certs/server.pem')), // 服务端证书 
    private_key: readFileSync(join(__dirname, '../certs/server-key.pem')) // 私钥 
  }],
  false // 不强制验证客户端证书 
);
 
// 在main.ts中应用 
const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.GRPC,
  options: {
    credentials: serverCredentials,
    package: 'hero',
    protoPath: join(__dirname, 'proto/hero.proto'),
  },
});
typescript 复制代码
// 客户端SSL配置(NestJS)
const clientCredentials = ChannelCredentials.createSsl(
  readFileSync(join(__dirname, '../certs/ca.pem')) // 仅需CA证书 
);
 
ClientModule.register([{
  name: 'HERO_PACKAGE',
  transport: Transport.GRPC,
  options: {
    package: 'hero',
    protoPath: join(__dirname, '../proto/hero.proto'),
    credentials: clientCredentials, // 注入凭证 
    url: '0.0.0.0:50000'
  }
}])

gRPC安全通信进阶实践

证书链问题解决方案

当出现UNAVAILABLE:Failed to get issuer certificate错误时,表明证书链验证失败

根本原因是CA证书未包含完整的信任链:

1 ) 获取根证书

bash 复制代码
下载缺失的根证书(以ZeroSSL为例)
wget -O certs/root.pem https://secure.trust-provider.com/products/root-certificates/root.crt 
 
合并证书链
cat certs/ca.pem certs/root.pem > certs/full-chain.pem

2 ) 客户端配置修正

typescript 复制代码
// 客户端证书配置(client.module.ts)
import { ChannelCredentials } from '@grpc/grpc-js';

const credentials = ChannelCredentials.createSsl(
  fs.readFileSync(join(__dirname, '../certs/full-chain.pem')),
);
 
@Client({
  transport: Transport.GRPC,
  options: {
    package: 'hero',
    protoPath: join(__dirname, '../proto/hero.proto'),
    url: '0.0.0.0:50000',
    credentials
  }
})

证书验证机制

组件 作用 安全级别
根证书 信任链顶端,自签名证书 最高
中间证书 由根证书签发,用于签发终端证书
终端实体证书 服务端实际使用的证书 标准

同样,如果客户端报错UNABLE_TO_VERIFY_LEAF_SIGNATURE ,根因:CA证书未包含完整证书链(缺失根证书)

解决方案:

  • 通过OpenSSL验证证书链完整性:

    bash 复制代码
    openssl s_client -connect example.com:50100 -CAfile ./certs/ca.pem 
  • 补全缺失根证书(如ZeroSSL根证书):

    bash 复制代码
    curl -o ./certs/root_ca.pem https://secure.trust-provider.com/root_ca.pem 
    cat ./certs/root_ca.pem >> ./certs/ca.pem  # 追加到CA文件 

gRPC服务测试方案全解析

方案一:grpcurl工具(Go环境)

环境配置

bash 复制代码
安装Go版本管理工具
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
 
安装Go 1.22+版本
gvm install go1.22 -B
gvm use go1.22 --default
 
安装grpcurl 
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

测试命令示例

bash 复制代码
非加密测试 
grpcurl -plaintext -proto proto/hero.proto -d '{"id":1}' localhost:50000 hero.HeroService/FindOne
 
SSL加密测试 
grpcurl -cacert certs/full-chain.pem -proto proto/hero.proto -d '{"id":1}' app.grpc.example.com:50000 hero.HeroService/FindOne

方案二:grpc-tools + ts-proto

生成测试客户端

bash 复制代码
npm install grpc-tools ts-proto 
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./generated \
                       --grpc_out=grpc_js:./generated \
                       --proto_path=proto proto/hero.proto 

Node.js测试脚本

typescript 复制代码
import * as grpc from '@grpc/grpc-js';
import { HeroServiceClient } from './generated/hero_grpc_pb';
import { HeroById, Hero } from './generated/hero_pb';

const client = new HeroServiceClient(
  'localhost:50010',
  grpc.credentials.createSsl(fs.readFileSync('certs/ca.pem'))
);

const request = new HeroById();
request.setId(1);

client.findOne(request, (err, response: Hero) => {
  if (err) console.error(err);
  else console.log(response.toObject()); // 输出: { id: 1, name: 'John' }
});

方案三:Node.js原生客户端

typescript 复制代码
// test-client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { join } from 'path';
import fs from 'fs';
 
const PROTO_PATH = join(__dirname, 'proto/hero.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
 
const client = new protoDescriptor.hero.HeroService(
  'localhost:50000',
  grpc.credentials.createSsl(
    fs.readFileSync(join(__dirname, 'certs/full-chain.pem'))
);
 
client.findOne({ id: 1 }, (err, response) => {
  if (err) console.error(err);
  else console.log(`Received: ${JSON.stringify(response)}`);
});

方案四:自动化测试工具链

typescript 复制代码
// grpc-tester.ts 
import { Test } from '@nestjs/testing';
import { ClientGrpc } from '@nestjs/microservices';
import { HeroServiceClient } from './generated/hero';
 
describe('gRPC Test Suite', () => {
  let client: HeroServiceClient;
 
  beforeAll(async () => {
    const module = await Test.createTestingModule({
      imports: [GrpcClientModule] // 自定义的gRPC客户端模块
    }).compile();
 
    const app = module.createNestMicroservice({});
    await app.init();
 
    client = module.get<ClientGrpc>('HERO_PACKAGE')
      .getService<HeroServiceClient>('HeroService');
  });
 
  it('should return valid hero data', async () => {
    const response = await firstValueFrom(client.findOne({ id: 1 }));
    expect(response).toEqual({ id: 1, name: 'Superman' });
  });
});

关键问题解决方案总结

1 ) 证书链完整性验证

  • 使用OpenSSL诊断:openssl s_client -connect domain.com:50000 -CAfile certs/full-chain.pem
  • 确保包含从终端证书到根证书的完整链

2 ) NestJS版本兼容处理

typescript 复制代码
// 解决"@grpc/grpc-js"版本警告 
import { credentials } from '@grpc/grpc-js';
const serverCredentials = credentials.createSsl(...);

3 ) Proto文件实时监控

json 复制代码
// nest-cli.json
{
  "compilerOptions": {
    "assets": ["/*.proto"],
    "watchAssets": true 
  }
}

关键问题总结

问题类型 解决方案
Proto转TS类型 VSCode插件快速生成 / TS-Proto工具链生成完整桩代码
SSL验证失败 补全CA证书链,通过OpenSSL调试,确保证书包含完整根证书
客户端类型安全 使用ClientGrpcProxy.getService()注入强类型服务接口
流式通信支持 在.proto中定义stream参数,使用@GrpcStreamMethod处理双向流

最佳实践

  1. 证书管理:使用ACME.sh自动续签证书,确保证书链完整
  2. 类型同步:将proto文件生成步骤加入构建流程(npm scripts)
  3. 错误处理:在gRPC客户端拦截器中统一处理UNAVAILABLEPERMISSION_DENIED错误码

总结与注意事项

1 ) 核心优化点:

  • 协议一致性:.protopackageservice名称需与代码严格对应。
  • 证书链完整性:CA根证书必须包含所有中间证书,否则触发链验证失败。
  • 类型安全:通过ts-proto生成的接口确保RPC方法类型匹配。

2 ) 性能与安全平衡:

  • 非生产环境可使用createInsecure()快速测试。
  • 生产环境必须启用TLS,并定期轮换证书。

3 ) 扩展建议:

  • 流式通信:gRPC支持stream关键字实现双向流通信(如实时日志推送)。
  • 拦截器:NestJS的ClientInterceptor可统一处理认证/日志逻辑。

性能优化提示:在微服务集群中使用gRPC时,启用HTTP/2多路复用和连接池复用可提升30%以上的吞吐量。通过@grpc/grpc-jschannelOptions配置grpc.max_concurrent_streams参数优化并发流数量。

完整解决方案体现了协议层安全与开发效率的平衡,解决了从基础通信到生产级安全部署的全链路难题,为微服务架构提供了企业级通信基础。

相关推荐
qq_5470261793 小时前
微服务 - 网关统一鉴权
运维·网络·微服务
常先森3 小时前
【解密源码】 RAGFlow 切分最佳实践- naive parser 语义切块(pdf 篇)
架构·llm·agent
MicroTech20253 小时前
微算法科技(NASDAQ MLGO):以隐私计算区块链筑牢多方安全计算(MPC)安全防线
科技·安全·区块链
星哥说事3 小时前
分布式存储:Ceph、GlusterFS、MinIO架构与部署
分布式·ceph·架构
重铸码农荣光4 小时前
从零搭建博客小程序:吃透配置、架构与核心原理,新手也能轻松上手
微信小程序·架构
落一落,掉一掉4 小时前
第十五周Fscan和利用漏洞上线远程和数据库提权上线远控
安全·web安全
陈果然DeepVersion5 小时前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(九)
java·人工智能·spring boot·微服务·kafka·面试题·rag
leijiwen5 小时前
S11e Protocol 数字身份体系架构白皮书
架构·web3·区块链·品牌·rwa
深耕AI5 小时前
【端口的核心区别】阿里云安全组:80、443和16251
安全·阿里云·云计算