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 }
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524