Fork me on GitHub

gRPC asp.net core自定义策略认证

在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

安装NuGet包

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

添加引用 GRPCDemo01Entity项目

安装NuGet包

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

 

添加引用 GRPCDemo01Entity项目

安装NuGet包

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

添加引用 GRPCDemo01Entity项目

Starup的ConfigureServices添加如下代码

1 //添加Grpc客户端
2  services.AddGrpcClient<Goodser.GoodserClient>(o =>
3  {
4        o.Address = new Uri("https://localhost:5001");
5  });

调用gRPC

 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 }

 

posted @ 2019-11-24 10:04  桂素伟  阅读(1093)  评论(0编辑  收藏  举报