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

posted on   dz45693  阅读(1959)  评论(0编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2014-01-06 sharepoint 2010 自定义页面布局

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示