書接上文 Go Grpc Jwt身份認證和Gateway集成以及HTTPS雙向認證, 那么它在asp.net core 里面怎么實現了, 前面asp.ner core 5.0 Grpc雙向認證 和 restful api包裝 外加swagger啟用【VSCode創建】已經完成了大部分, 我們只要引入jwt 和跨域就了, 網上很多文章 都是用IdentityServer4【個人實際生產中沒有使用過, 感覺比較中】。本文保持和go一至的風格,增加一個login方法
1.修改grpcserver\Protos\greet.proto 文件如下【客戶端的文件不需要option部分】:
syntax = "proto3"; import "google/api/annotations.proto"; option csharp_namespace = "grpcserver"; package greet; // The greeting service definition. service Greeter { rpc Login (LoginRequest) returns (LoginReply) { option (google.api.http) = { post: "/v1/greeter/login" body: "*" }; } rpc SayHello (HelloRequest) returns (HelloReply) { option (google.api.http) = { get: "/v1/greeter/{name}" }; } } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings. message HelloReply { string message = 1; } message LoginRequest{ string username=1; string password=2; } message LoginReply{ string status=1; string token=2; }
客戶端grpcclient\Protos\greet.proto 如下:
syntax = "proto3"; option csharp_namespace = "grpcserver"; package greet; // The greeting service definition. service Greeter { rpc Login (LoginRequest) returns (LoginReply); rpc SayHello (HelloRequest) returns (HelloReply); } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings. message HelloReply { string message = 1; } message LoginRequest{ string username=1; string password=2; } message LoginReply{ string status=1; string token=2; }
2.要使用jwt 需要增加相應的包Microsoft.AspNetCore.Authentication.JwtBearer,和go相同, 我們同樣創建一個 創建token 和通過token 獲取用戶名的方法, 整個grpcserver\Services\GreeterService.cs如下:
using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Grpc.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; namespace grpcserver { public class GreeterService : Greeter.GreeterBase { private readonly ILogger<GreeterService> _logger; const string tokenSchema = "Bearer"; const string tokenIssuer = "https://localhost:5001"; const string tokenAudience = "grpc"; const string tokenSecurityKey = "asp.netcore5.0grpcjwt"; public GreeterService(ILogger<GreeterService> logger) { _logger = logger; } public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { var userName= CheckAuth(context)??string.Empty; return Task.FromResult(new HelloReply { Message = $"Hello {request.Name } Request by:{userName}" }); } public override Task<LoginReply> Login(LoginRequest request, ServerCallContext context) { LoginReply reply = new LoginReply { Status = "401", Token = "�û�������������Ч" }; if (request.Username == "gavin" && request.Password == "gavin") { reply.Token = CreateToken(request.Username); reply.Status = "200"; } Console.WriteLine($"call login: username:{request.Username} ,password:{request.Password}, return token:{reply.Token}"); return Task.FromResult(reply); } string CreateToken(string userName) { var claim = new Claim[] { new Claim(ClaimTypes.Name,userName) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSecurityKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(tokenIssuer, tokenAudience, claim, DateTime.Now, DateTime.Now.AddMinutes(60),creds); var tokenStr = new JwtSecurityTokenHandler().WriteToken(token); return tokenStr; } string CheckAuth(ServerCallContext context) { string tokenStr= context.GetHttpContext().Request?.Headers["Authorization"]; if (string.IsNullOrEmpty(tokenStr)) { return string.Empty; } if (tokenStr.StartsWith(tokenSchema)) { tokenStr = tokenStr.Split(' ')[1]; } SecurityToken token; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSecurityKey)); ClaimsPrincipal claims= new JwtSecurityTokenHandler().ValidateToken(tokenStr, new TokenValidationParameters { ValidAudience=tokenAudience, ValidIssuer=tokenIssuer , IssuerSigningKey=key }, out token); var userName = claims.FindFirstValue(ClaimTypes.Name); return userName; } } }
3.修改客戶端的調用如下【由於客戶端 這次調用的是https 所以grpcserver\Program.cs的方法CreateHostBuilder 在監聽5001的時候 需要這只 協議 listenOptions.Protocols = HttpProtocols.Http1AndHttp2;】:
using System; using System.Net.Http; using System.Threading.Tasks; using grpcserver; using Grpc.Net.Client; using System.Security.Cryptography.X509Certificates; using System.Security.Authentication; using Grpc.Core; using Newtonsoft.Json; namespace grpcclient { class Program { const string url = "https://localhost:5001"; const string tokenSchema = "Bearer"; static void Main(string[] args) { GrpcCall(); Console.WriteLine("http start................"); /// HttpCall(); } static void GrpcCall() { var channel = GrpcChannel.ForAddress(url, new GrpcChannelOptions { HttpHandler = GetHttpHandler() }); var client = new Greeter.GreeterClient(channel); var loginReplay = client.Login(new LoginRequest { Username="gavin", Password="gavin"}); string token = loginReplay.Token; //Console.WriteLine("get token:" + token); var headers = new Metadata(); headers.Add("Authorization", $"{tokenSchema} {token}"); var reply = client.SayHello(new HelloRequest { Name = "GreeterClient" },headers); Console.WriteLine("SayHello 1: " + reply.Message); /// client = new Greeter.GreeterClient(GetChannel(url, token)); reply = client.SayHello(new HelloRequest { Name = "GreeterClient2" }); Console.WriteLine("SayHello 2: " + reply.Message); } static void HttpCall() { var httpclient = new HttpClient(GetHttpHandler()); var loginRequest = "{\"username\":\"gavin\",\"password\":\"gavin\"}"; HttpContent content = new StringContent(loginRequest); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); var response= httpclient.PostAsync(url + "/v1/greeter/login", content).Result; response.EnsureSuccessStatusCode();//用來拋異常的 var loginResponseStr= response.Content.ReadAsStringAsync().Result; var token= JsonConvert.DeserializeObject<LoginReply>(loginResponseStr).Token; // HttpRequestMessage request = new HttpRequestMessage(); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(tokenSchema, token); request.RequestUri = new Uri(url + "/v1/greeter/gavin"); var helloResponse = httpclient.Send(request); var helloResponseStr = helloResponse.Content.ReadAsStringAsync().Result; Console.WriteLine(helloResponseStr); } static HttpClientHandler GetHttpHandler() { var handler = new HttpClientHandler() { SslProtocols = SslProtocols.Tls12, ClientCertificateOptions = ClientCertificateOption.Manual, ServerCertificateCustomValidationCallback = (message, cer, chain, errors) => { return chain.Build(cer); } }; var path = AppDomain.CurrentDomain.BaseDirectory + "cert\\client.pfx"; var crt = new X509Certificate2(path, "123456789"); handler.ClientCertificates.Add(crt); return handler; } static GrpcChannel GetChannel(string address, string token) { var credentials = CallCredentials.FromInterceptor((context, metadata) => { if (!string.IsNullOrEmpty(token)) { metadata.Add("Authorization", $"{tokenSchema} {token}"); } return Task.CompletedTask; }); var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions { Credentials = ChannelCredentials.Create(new SslCredentials(), credentials), HttpHandler=GetHttpHandler() }); return channel; } } }
4.為了保證跨域請求, 我們在grpcserver\Startup.cs的ConfigureServices方法 添加 如下:
services.AddCors(o => o.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding"); }));
在Configure 方法調用 app.UseCors("AllowAll");
5.運行結果:
D:\Users\gavin\Documents\DotNetCoreSample\asp.netgrpccert\grpcclient>dotnet run SayHello 1: Hello GreeterClient Request by:gavin SayHello 2: Hello GreeterClient2 Request by:gavin http start................ { "message": "Hello gavin Request by:gavin" }
下載地址:
https://github.com/dz45693/asp.netgrpccert.git