[微服务] 为 gRPC 服务添加 JWT 认证,使用 Apifox 客户端工具 进行测试
gRPC 测试客户端工具
Apifox = Postman + Swagger + Mock + JMeter
https://apifox.com/
关于为何使用 gRPC?
相较于 REST 使用 gRPC 服务具有以下优势:
性能方面
- 高效的通信协议
gRPC 基于 HTTP/2 协议,而 RESTful API 通常基于 HTTP/1.1。HTTP/2 支持多路复用,能在一个 TCP 连接上同时发送多个请求和响应,大大提高了通信效率,减少了连接建立和销毁的开销。
例如,在一个需要频繁交互的实时数据监控系统中,gRPC 的多路复用特性使得大量数据能够快速且并发地传输,而不会像 HTTP/1.1 那样因频繁创建连接而导致性能瓶颈。
- 更小的消息体积
gRPC 默认使用 Protocol Buffers 作为序列化格式,它比 RESTful API 常用的 JSON 格式更加紧凑和高效。
比如传输相同的结构化数据,使用 Protocol Buffers 序列化后的字节数通常会比 JSON 少很多,这在网络传输中能显著减少数据量,提高传输速度,降低带宽消耗。
代码生成和类型安全
- 自动代码生成
gRPC 通过.proto 文件定义服务和消息类型,然后可以使用 protobuf 编译器自动生成客户端和服务器端的代码。
这避免了手动编写大量的序列化和反序列化代码,减少了开发工作量和出错的可能性。
而在 RESTful API 中,通常需要使用第三方库来进行 JSON 等格式的序列化和反序列化,且代码相对更繁琐。
- 强类型约束
由.proto 文件生成的代码具有强类型特性,在编译时就能发现类型不匹配等错误,而不是在运行时才暴露问题。
例如,如果一个函数期望接收一个整数类型的参数,传入字符串就会在编译阶段报错,有助于提高代码的稳定性和可维护性。
相比之下,RESTful API 在处理请求和响应时,对数据类型的约束相对较弱,容易出现类型错误。
服务定义和 IDL
- 清晰的服务定义
gRPC 使用.proto 文件以接口描述语言(IDL)的形式明确定义服务的接口、方法签名、请求和响应消息结构等。
这种清晰的服务定义使得开发团队在开发客户端和服务器端时能更好地协同工作,避免了因对接口理解不一致而产生的问题。
而 RESTful API 的接口定义通常分散在代码和文档中,不够集中和明确。
- 跨语言支持
由于.proto 文件是与语言无关的,gRPC 支持多种编程语言,方便不同语言编写的客户端和服务器端之间进行通信。
例如,可以用 C# 编写 gRPC 服务器,同时用 Python 或 Java 编写客户端来调用该服务,这在构建多语言的分布式系统时非常有优势。
而 RESTful API 虽然也能实现跨语言调用,但在接口定义和数据格式处理上可能会因语言的不同而遇到更多兼容性问题。
流处理能力
- 双向流支持
gRPC 支持双向流,客户端和服务器端可以同时向对方发送数据流。
例如在实时视频会议系统中,gRPC 的双向流功能可以让客户端实时上传视频数据的同时,服务器端也能及时下发其他参会者的视频数据和控制指令,实现高效的实时交互。
相比之下,RESTful API 通常以请求 - 响应模式为主,对双向流的支持相对较弱。
操作范例
1. 创建 gRPC 项目并添加 new proto 文件
syntax = "proto3"; option csharp_namespace = "Todo_GrpcService.Protos"; package todo; /* 定义服务接口 */ service ITodoService { rpc NewTodo(NewTodoRequest) returns (NewTodoReply); rpc GetTodoList(GetTodoListRequest) returns (stream GetTodoListReply); } /* 定义服务 message 参数类型 */ message NewTodoRequest { int32 ID = 1; string Name = 2; bytes data = 3; } message NewTodoReply { bool Status = 1; string Message = 2; } message GetTodoListRequest { } message GetTodoListReply { bool Status = 1; string Name = 2; }
2. 编辑项目的 Project File,开放对所有 Protobuf 文件的自动编译
<Protobuf Include="Protos\*.proto" GrpcServices="Server" />
3. 创建 new service,重载并实现 proto 接口的业务逻辑
4. 编辑项目 Program.cs 文件,将 TodoService 服务添加到路由管道
app.MapGrpcService<TodoService>();
如何对 gRPC 服务进行测试?
5. 使用 Apifox 测试 gRPC 服务,导入 .proto 文件
6. 运行服务进行测试
如何对 gRPC 集成 JWT 身份认证?
7. 项目添加 Nuget 引用 "Microsoft.AspNetCore.Authentication.JwtBearer"
8. 创建 auth.proto
syntax = "proto3"; option csharp_namespace = "Todo_GrpcService.Protos"; package auth; /* 定义服务接口 */ service IAuth { rpc GetToken (AuthData) returns (AuthResult); rpc ConnectTest(Empty) returns (AuthSuccess); } /* 定义服务 message 参数类型 */ message AuthData { string name = 1; string pass = 2; } message AuthResult { int32 code = 1; string token = 2; string message = 3; } message Empty { } message AuthSuccess { string message = 1; }
9. 创建 AuthService.cs
using Grpc.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Todo_GrpcService.Protos; namespace Todo_GrpcService.Services { public class AuthService : IAuth.IAuthBase { public override Task<AuthResult> GetToken(AuthData request, ServerCallContext context) { Console.OutputEncoding = Encoding.UTF8; if (request.Name == "test_username" && request.Pass == "test_password") { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, "0"), new Claim(ClaimTypes.Name, "test_username") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("This_Is_Lonooooooooooooooooooooooooooooooooooooooooooooooooooog_Security_Key_String")); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: "test_jwt_issuer", audience: "test_jwt_audience", claims: claims, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddSeconds(10), signingCredentials: creds); var stoken = new JwtSecurityTokenHandler().WriteToken(token); return Task.FromResult(new AuthResult { Code = 1, Message = "Auth Success", Token = stoken }); } return Task.FromResult(new AuthResult { Code = -1, Message = "Auth Failed" }); } [Authorize] public override Task<AuthSuccess> ConnectTest(Empty request, ServerCallContext context) { Console.OutputEncoding = Encoding.UTF8; var user = context.GetHttpContext().User; Console.WriteLine("Connect Success"); return Task.FromResult(new AuthSuccess { Message = "Connect Success" }); } } }
10. 编辑项目 Program.cs 文件,将 AuthService 添加到路由管道
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Todo_GrpcService.Services; namespace Todo_GrpcService { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); /* Working for JWT */ builder.Services.AddAuthentication(t => { t.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; t.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(t => { t.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = "test_jwt_issuer", ValidAudience = "test_jwt_audience", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("This_Is_Lonooooooooooooooooooooooooooooooooooooooooooooooooooog_Security_Key_String")) }; }); builder.Services.AddAuthorization(); // Add services to the container. builder.Services.AddGrpc(); var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGrpcService<GreeterService>(); app.MapGrpcService<TodoService>(); /* Working for JWT */ app.MapGrpcService<AuthService>(); app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); app.Run(); } } }
11. 使用 Apifox 测试 <未认证的 Case>
- 获取 Token 失败
- 直接访问服务失败
12. 使用 Apifox 测试 <通过认证的 Case>
- 获取 Token 成功
- 访问服务成功
参考教程
gRPC入门与实操(.NET篇)
https://www.cnblogs.com/newton/p/17033789.html
gRPC四种模式、认证和授权实战演示,必赞~~~
https://www.cnblogs.com/zoe-zyq/p/15004843.html
.Net Core gRPC入门
https://blog.csdn.net/qq_31803267/category_10082392.html
豆包 - 全能 AI 助手
https://www.doubao.com/chat/