在GitHub上有個項目,本來是作為自己研究學習.net core的Demo,沒想到很多同學在看,還給了很多星,所以覺得應該升成3.0,整理一下,寫成博分享給學習.net core的同學們。
項目名稱:Asp.NetCoreExperiment
項目地址:https://github.com/axzxs2001/Asp.NetCoreExperiment
本案例Github代碼庫
https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/GRPC
關於gRPC參考https://grpc.io/
在 gRPC asp.net core項目模板下引入自定義策略認證,代碼如下
創建共享proto
創建.NET Standard庫項目GRPCDemo01Entity
安裝NuGet包
Google.Protobuf
Grpc.Core
Grpc.Tools
goods.proto代碼如下
1 syntax = "proto3"; 2 3 option csharp_namespace = "GRPCDemo01Entity"; 4 5 package Goods; 6 7 service Goodser { 8 //查詢 9 rpc GetGoods (QueryRequest) returns (QueryResponse); 10 //登錄 11 rpc Login (LoginRequest) returns (LoginResponse); 12 } 13 //查詢參數 14 message QueryRequest { 15 string name = 1; 16 } 17 //查詢反回值 18 message QueryResponse { 19 string name = 1; 20 int32 quantity=2; 21 } 22 //登錄參數 23 message LoginRequest{ 24 string username=1; 25 string password=2; 26 } 27 //登錄返回值 28 message LoginResponse{ 29 bool result=1; 30 string message=2; 31 string token=3; 32 }
.csproj中配置
<ItemGroup> <Protobuf Include="Protos\goods.proto" /> </ItemGroup>
創建GRPC asp.net core service
安裝NuGet包
Grpc.AspNetCore
Microsoft.AspNetCore.Authentication.JwtBearer
添加引用 GRPCDemo01Entity項目
設置配置文件appsettings.json
{ "Logging": { "LogLevel": { "Default": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "Kestrel": { "EndpointDefaults": { "Protocols": "Http2" } }, "Audience": { "Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", "Issuer": "gsw", "Audience": "everone" } }
添加四個自定義策略認證相關文件
Permission.cs
1 namespace GRPCDemo01Service 2 { 3 /// <summary> 4 /// 用戶或角色或其他憑據實體 5 /// </summary> 6 public class Permission 7 { 8 /// <summary> 9 /// 用戶或角色或其他憑據名稱 10 /// </summary> 11 public virtual string Name 12 { get; set; } 13 /// <summary> 14 /// 請求Url 15 /// </summary> 16 public virtual string Url 17 { get; set; } 18 } 19 }
JwtToken.cs
1 using System; 2 using System.IdentityModel.Tokens.Jwt; 3 using System.Security.Claims; 4 5 namespace GRPCDemo01Service 6 { 7 public class JwtToken 8 { 9 /// <summary> 10 /// 獲取基於JWT的Token 11 /// </summary> 12 /// <param name="username"></param> 13 /// <returns></returns> 14 public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) 15 { 16 var now = DateTime.UtcNow; 17 var jwt = new JwtSecurityToken( 18 issuer: permissionRequirement.Issuer, 19 audience: permissionRequirement.Audience, 20 claims: claims, 21 notBefore: now, 22 expires: now.Add(permissionRequirement.Expiration), 23 signingCredentials: permissionRequirement.SigningCredentials 24 ); 25 var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); 26 var response = new 27 { 28 Status = true, 29 access_token = encodedJwt, 30 expires_in = permissionRequirement.Expiration.TotalMilliseconds, 31 token_type = "Bearer" 32 }; 33 return response; 34 } 35 } 36 }
PermissionHandler.cs
1 using Microsoft.AspNetCore.Authentication; 2 using Microsoft.AspNetCore.Authorization; 3 using System.Linq; 4 using System.Security.Claims; 5 using System.Threading.Tasks; 6 using System; 7 using Microsoft.AspNetCore.Routing; 8 9 namespace GRPCDemo01Service 10 { 11 /// <summary> 12 /// 權限授權Handler 13 /// </summary> 14 public class PermissionHandler : AuthorizationHandler<PermissionRequirement> 15 { 16 17 /// <summary> 18 /// 驗證方案提供對象 19 /// </summary> 20 public IAuthenticationSchemeProvider Schemes { get; set; } 21 22 /// <summary> 23 /// 構造 24 /// </summary> 25 /// <param name="schemes"></param> 26 public PermissionHandler(IAuthenticationSchemeProvider schemes) 27 { 28 Schemes = schemes; 29 } 30 /// <summary> 31 /// 驗證每次請求 32 /// </summary> 33 /// <param name="context"></param> 34 /// <param name="requirement"></param> 35 /// <returns></returns> 36 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) 37 { 38 if (context.Resource is RouteEndpoint route && route != null) 39 { 40 var questUrl = route.RoutePattern.RawText?.ToLower(); 41 if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0) 42 { 43 var name = context.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType)?.Value; 44 //驗證權限 45 if (requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() > 0) 46 { //判斷過期時間 47 if (DateTime.Parse(context.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now) 48 { 49 context.Succeed(requirement); 50 return Task.CompletedTask; 51 } 52 } 53 } 54 } 55 context.Fail(); 56 return Task.CompletedTask; 57 } 58 } 59 }
PermissionRequirement.cs
1 using Microsoft.AspNetCore.Authorization; 2 using Microsoft.IdentityModel.Tokens; 3 using System; 4 using System.Collections.Generic; 5 6 namespace GRPCDemo01Service 7 { 8 /// <summary> 9 /// 必要參數類 10 /// </summary> 11 public class PermissionRequirement : IAuthorizationRequirement 12 { 13 /// <summary> 14 /// 用戶權限集合 15 /// </summary> 16 public List<Permission> Permissions { get; private set; } 17 /// <summary> 18 /// 無權限action 19 /// </summary> 20 public string DeniedAction { get; set; } 21 22 /// <summary> 23 /// 認證授權類型 24 /// </summary> 25 public string ClaimType { internal get; set; } 26 /// <summary> 27 /// 請求路徑 28 /// </summary> 29 public string LoginPath { get; set; } = "/Api/Login"; 30 /// <summary> 31 /// 發行人 32 /// </summary> 33 public string Issuer { get; set; } 34 /// <summary> 35 /// 訂閱人 36 /// </summary> 37 public string Audience { get; set; } 38 /// <summary> 39 /// 過期時間 40 /// </summary> 41 public TimeSpan Expiration { get; set; } 42 /// <summary> 43 /// 簽名驗證 44 /// </summary> 45 public SigningCredentials SigningCredentials { get; set; } 46 47 /// <summary> 48 /// 構造 49 /// </summary> 50 /// <param name="deniedAction">無權限action</param> 51 /// <param name="userPermissions">用戶權限集合</param> 52 53 /// <summary> 54 /// 構造 55 /// </summary> 56 /// <param name="deniedAction">拒約請求的url</param> 57 /// <param name="permissions">權限集合</param> 58 /// <param name="claimType">聲明類型</param> 59 /// <param name="issuer">發行人</param> 60 /// <param name="audience">訂閱人</param> 61 /// <param name="signingCredentials">簽名驗證實體</param> 62 public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration) 63 { 64 ClaimType = claimType; 65 DeniedAction = deniedAction; 66 Permissions = permissions; 67 Issuer = issuer; 68 Audience = audience; 69 Expiration = expiration; 70 SigningCredentials = signingCredentials; 71 } 72 } 73 }
設置Startup.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Security.Claims; 4 using System.Text; 5 using Microsoft.AspNetCore.Authentication.JwtBearer; 6 using Microsoft.AspNetCore.Authorization; 7 using Microsoft.AspNetCore.Builder; 8 using Microsoft.AspNetCore.Hosting; 9 using Microsoft.AspNetCore.Http; 10 using Microsoft.Extensions.Configuration; 11 using Microsoft.Extensions.DependencyInjection; 12 using Microsoft.Extensions.Hosting; 13 using Microsoft.IdentityModel.Tokens; 14 15 namespace GRPCDemo01Service 16 { 17 public class Startup 18 { 19 public Startup(IConfiguration configuration) 20 { 21 Configuration = configuration; 22 } 23 public IConfiguration Configuration { get; } 24 public void ConfigureServices(IServiceCollection services) 25 { 26 //讀取配置文件 27 var audienceConfig = Configuration.GetSection("Audience"); 28 var symmetricKeyAsBase64 = audienceConfig["Secret"]; 29 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); 30 var signingKey = new SymmetricSecurityKey(keyByteArray); 31 var tokenValidationParameters = new TokenValidationParameters 32 { 33 ValidateIssuerSigningKey = true, 34 IssuerSigningKey = signingKey, 35 ValidateIssuer = true, 36 ValidIssuer = audienceConfig["Issuer"],//發行人 37 ValidateAudience = true, 38 ValidAudience = audienceConfig["Audience"],//訂閱人 39 ValidateLifetime = true, 40 ClockSkew = TimeSpan.Zero, 41 RequireExpirationTime = true, 42 }; 43 var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); 44 //這個集合模擬用戶權限表,可從數據庫中查詢出來 45 var permission = new List<Permission> { 46 new Permission { Url="/Goods.Goodser/GetGoods", Name="admin"}, 47 new Permission { Url="systemapi", Name="system"} 48 }; 49 //如果第三個參數,是ClaimTypes.Role,上面集合的每個元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個元素的Name為用戶名 50 var permissionRequirement = new PermissionRequirement( 51 "/api/denied", permission, 52 ClaimTypes.Role, 53 audienceConfig["Issuer"], 54 audienceConfig["Audience"], 55 signingCredentials, 56 expiration: TimeSpan.FromSeconds(10000)//設置Token過期時間 57 ); 58 59 services.AddAuthorization(options => 60 { 61 options.AddPolicy("Permission", policy => policy.AddRequirements(permissionRequirement)); 62 }). 63 AddAuthentication(options => 64 { 65 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 66 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 67 }) 68 .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => 69 { 70 //不使用https 71 o.RequireHttpsMetadata = true; 72 o.TokenValidationParameters = tokenValidationParameters; 73 }); 74 //注入授權Handler 75 services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); 76 services.AddSingleton(permissionRequirement); 77 services.AddGrpc(); 78 } 79 80 81 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 82 { 83 if (env.IsDevelopment()) 84 { 85 app.UseDeveloperExceptionPage(); 86 } 87 88 app.UseRouting(); 89 app.UseAuthentication(); 90 app.UseAuthorization(); 91 app.UseEndpoints(endpoints => 92 { 93 endpoints.MapGrpcService<GoodsService>(); 94 95 endpoints.MapGet("/", async context => 96 { 97 await context.Response.WriteAsync("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"); 98 }); 99 }); 100 } 101 } 102 }
GoodsService.cs
1 using System; 2 using System.Security.Claims; 3 using System.Threading.Tasks; 4 using Grpc.Core; 5 using GRPCDemo01Entity; 6 using Microsoft.AspNetCore.Authorization; 7 using Microsoft.Extensions.Logging; 8 9 namespace GRPCDemo01Service 10 { 11 [Authorize("Permission")] 12 public class GoodsService : Goodser.GoodserBase 13 { 14 private readonly ILogger<GoodsService> _logger; 15 readonly PermissionRequirement _requirement; 16 public GoodsService(ILogger<GoodsService> logger, PermissionRequirement requirement) 17 { 18 _requirement = requirement; 19 _logger = logger; 20 } 21 public override Task<QueryResponse> GetGoods(QueryRequest request, ServerCallContext context) 22 { 23 return Task.FromResult(new QueryResponse 24 { 25 Name = "Hello " + request.Name, 26 Quantity = 10 27 }); 28 } 29 [AllowAnonymous] 30 public override Task<LoginResponse> Login(LoginRequest user, ServerCallContext context) 31 { 32 //todo 查詢數據庫核對用戶名密碼 33 var isValidated = user.Username == "gsw" && user.Password == "111111"; 34 if (!isValidated) 35 { 36 return Task.FromResult(new LoginResponse() 37 { 38 Message = "認證失敗" 39 }); 40 } 41 else 42 { 43 //如果是基於用戶的授權策略,這里要添加用戶;如果是基於角色的授權策略,這里要添加角色 44 var claims = new Claim[] { 45 new Claim(ClaimTypes.Name, user.Username), 46 new Claim(ClaimTypes.Role, "admin"), 47 new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) 48 }; 49 50 var token = JwtToken.BuildJwtToken(claims, _requirement); 51 return Task.FromResult(new LoginResponse() 52 { 53 Result = true, 54 Token = token.access_token 55 }); 56 57 } 58 } 59 } 60 }
控制台程序調用gRPC
添加引用 GRPCDemo01Entity項目
安裝NuGet包
Grpc.Net.Client
Program.cs
1 using Grpc.Core; 2 using Grpc.Net.Client; 3 using GRPCDemo01Entity; 4 using System; 5 using System.Threading.Tasks; 6 7 namespace GRPCDemo01Test 8 { 9 class Program 10 { 11 static async Task Main(string[] args) 12 { 13 while (true) 14 { 15 Console.WriteLine("用戶名:"); 16 var username = Console.ReadLine(); 17 Console.WriteLine("密碼:"); 18 var password = Console.ReadLine(); 19 var tokenResponse = await Login(username, password); 20 if (tokenResponse.Result) 21 { 22 await Query(tokenResponse.Token); 23 } 24 else 25 { 26 Console.WriteLine("登錄失敗"); 27 } 28 } 29 } 30 /// <summary> 31 /// 查詢 32 /// </summary> 33 /// <param name="token">token</param> 34 /// <returns></returns> 35 static async Task Query(string token) 36 { 37 token = $"Bearer {token }"; 38 var headers = new Metadata { { "Authorization", token } }; 39 var channel = GrpcChannel.ForAddress("https://localhost:5001"); 40 var client = new Goodser.GoodserClient(channel); 41 var query = await client.GetGoodsAsync( 42 new QueryRequest { Name = "桂素偉" }, headers); 43 Console.WriteLine($"返回值 Name:{ query.Name},Quantity:{ query.Quantity}"); 44 } 45 /// <summary> 46 /// 登錄 47 /// </summary> 48 /// <param name="userName">userName</param> 49 /// <param name="password">password</param> 50 /// <returns></returns> 51 static async Task<LoginResponse> Login(string userName, string password) 52 { 53 var channel = GrpcChannel.ForAddress("https://localhost:5001"); 54 var client = new Goodser.GoodserClient(channel); 55 var response = await client.LoginAsync( 56 new LoginRequest() { Username = userName, Password = password }); 57 return response; 58 } 59 } 60 }
webapi調用gRPC
添加引用 GRPCDemo01Entity項目
安裝NuGet包
Grpc.Net.ClientFactory
Starup的ConfigureServices添加如下代碼
1 //添加Grpc客戶端 2 services.AddGrpcClient<Goodser.GoodserClient>(o => 3 { 4 o.Address = new Uri("https://localhost:5001"); 5 });
調用gRPC
1 using System.Threading.Tasks; 2 using Grpc.Core; 3 using GRPCDemo01Entity; 4 using Microsoft.AspNetCore.Mvc; 5 using Microsoft.Extensions.Logging; 6 7 namespace GRPCDemo01WebTest.Controllers 8 { 9 [ApiController] 10 [Route("[controller]")] 11 public class WeatherForecastController : ControllerBase 12 { 13 private readonly ILogger<WeatherForecastController> _logger; 14 /// <summary> 15 /// 客戶端 16 /// </summary> 17 private readonly Goodser.GoodserClient _client; 18 public WeatherForecastController(ILogger<WeatherForecastController> logger, Goodser.GoodserClient client) 19 { 20 _client = client; 21 _logger = logger; 22 } 23 24 [HttpGet] 25 public async Task<string> Get() 26 { 27 //登錄 28 var tokenResponse = await _client.LoginAsync(new LoginRequest { Username = "gsw", Password = "111111" }); 29 var token = $"Bearer {tokenResponse.Token }"; 30 var headers = new Metadata { { "Authorization", token } }; 31 var request = new QueryRequest { Name = "桂素偉" }; 32 //查詢 33 var query = await _client.GetGoodsAsync(request, headers); 34 return $"Name:{query.Name},Quantity:{query.Quantity}"; 35 } 36 } 37 }
gRPC調用gRPC
添加引用 GRPCDemo01Entity項目
Starup的ConfigureServices添加如下代碼
1 //添加Grpc客戶端 2 services.AddGrpcClient<Goodser.GoodserClient>(o => 3 { 4 o.Address = new Uri("https://localhost:5001"); 5 });
調用gRPC
1 using System; 2 using System.Threading.Tasks; 3 using Grpc.Core; 4 using GRPCDemo01Entity; 5 using Microsoft.Extensions.Logging; 6 7 namespace GRPCDemo01GRPCTest 8 { 9 public class OrderService : Orderer.OrdererBase 10 { 11 private readonly ILogger<OrderService> _logger; 12 private readonly Goodser.GoodserClient _client; 13 public OrderService(ILogger<OrderService> logger, Goodser.GoodserClient client) 14 { 15 _client = client; 16 _logger = logger; 17 } 18 public override async Task<OrderResponse> GetGoods(OrderRequest request, ServerCallContext context) 19 { 20 //登錄 21 var tokenResponse = await _client.LoginAsync( 22 new LoginRequest() { 23 Username = "gsw", 24 Password = "111111" 25 }); 26 if (tokenResponse.Result) 27 { 28 var token = $"Bearer {tokenResponse.Token }"; 29 var headers = new Metadata { { "Authorization", token } }; 30 //查詢 31 var query = await _client.GetGoodsAsync( 32 new QueryRequest { Name = "桂素偉" }, headers); 33 Console.WriteLine($"返回值 Name:{ query.Name},Quantity:{ query.Quantity}"); 34 return new OrderResponse { Name = query.Name, Quantity = query.Quantity }; 35 } 36 else 37 { 38 Console.WriteLine("登錄失敗"); 39 return null; 40 } 41 } 42 } 43 }
