asp.ner core 5.0 Grpc HttpApi 和jwt的集成 和跨域【https雙向認證】

書接上文 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章