书接上文 Go Grpc Jwt身份认证和Gateway集成以及HTTPS双向认证, 那么它在asp.net core 里面怎么实现了, 前面asp.ner core 5.0 Grpc双向认证 和 restful api包装 外加swagger启用【VSCode创建】已经完成了大部分, 我们只要引入jwt 和跨域就了, 网上很多文章 都是用IdentityServer4【个人实际生产中没有使用过, 感觉比较中】。本文保持和go一至的风格,增加一个login方法
1.修改grpcserverProtosgreet.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; }
客户端grpcclientProtosgreet.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 获取用户名的方法, 整个grpcserverServicesGreeterService.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 所以grpcserverProgram.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.为了保证跨域请求, 我们在grpcserverStartup.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:UsersgavinDocumentsDotNetCoreSampleasp.netgrpccertgrpcclient>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