在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
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
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
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
1 //添加Grpc用戶端
2 services.AddGrpcClient<Goodser.GoodserClient>(o =>
3 {
4 o.Address = new Uri("https://localhost:5001");
5 });
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 }
****歡迎關注我的asp.net core系統課程**** 《asp.net core精要講解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core項目實戰》 https://ke.qq.com/course/291868
《基于.net core微服務》 https://ke.qq.com/course/299524