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代码如下
syntax = "proto3"; option csharp_namespace = "GRPCDemo01Entity"; package Goods; service Goodser {
//查询
rpc GetGoods (QueryRequest) returns (QueryResponse);
//登录
rpc Login (LoginRequest) returns (LoginResponse);
}
//查询参数
message QueryRequest {
string name = ;
}
//查询反回值
message QueryResponse {
string name = ;
int32 quantity=;
}
//登录参数
message LoginRequest{
string username=;
string password=;
}
//登录返回值
message LoginResponse{
bool result=;
string message=;
string token=;
}
.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
namespace GRPCDemo01Service
{
/// <summary>
/// 用户或角色或其他凭据实体
/// </summary>
public class Permission
{
/// <summary>
/// 用户或角色或其他凭据名称
/// </summary>
public virtual string Name
{ get; set; }
/// <summary>
/// 请求Url
/// </summary>
public virtual string Url
{ get; set; }
}
}
JwtToken.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; namespace GRPCDemo01Service
{
public class JwtToken
{
/// <summary>
/// 获取基于JWT的Token
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)
{
var now = DateTime.UtcNow;
var jwt = new JwtSecurityToken(
issuer: permissionRequirement.Issuer,
audience: permissionRequirement.Audience,
claims: claims,
notBefore: now,
expires: now.Add(permissionRequirement.Expiration),
signingCredentials: permissionRequirement.SigningCredentials
);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
Status = true,
access_token = encodedJwt,
expires_in = permissionRequirement.Expiration.TotalMilliseconds,
token_type = "Bearer"
};
return response;
}
}
}
PermissionHandler.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System;
using Microsoft.AspNetCore.Routing; namespace GRPCDemo01Service
{
/// <summary>
/// 权限授权Handler
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{ /// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; } /// <summary>
/// 构造
/// </summary>
/// <param name="schemes"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
}
/// <summary>
/// 验证每次请求
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.Resource is RouteEndpoint route && route != null)
{
var questUrl = route.RoutePattern.RawText?.ToLower();
if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > )
{
var name = context.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType)?.Value;
//验证权限
if (requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() > )
{ //判断过期时间
if (DateTime.Parse(context.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
}
context.Fail();
return Task.CompletedTask;
}
}
}
PermissionRequirement.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic; namespace GRPCDemo01Service
{
/// <summary>
/// 必要参数类
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用户权限集合
/// </summary>
public List<Permission> Permissions { get; private set; }
/// <summary>
/// 无权限action
/// </summary>
public string DeniedAction { get; set; } /// <summary>
/// 认证授权类型
/// </summary>
public string ClaimType { internal get; set; }
/// <summary>
/// 请求路径
/// </summary>
public string LoginPath { get; set; } = "/Api/Login";
/// <summary>
/// 发行人
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 订阅人
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public TimeSpan Expiration { get; set; }
/// <summary>
/// 签名验证
/// </summary>
public SigningCredentials SigningCredentials { get; set; } /// <summary>
/// 构造
/// </summary>
/// <param name="deniedAction">无权限action</param>
/// <param name="userPermissions">用户权限集合</param> /// <summary>
/// 构造
/// </summary>
/// <param name="deniedAction">拒约请求的url</param>
/// <param name="permissions">权限集合</param>
/// <param name="claimType">声明类型</param>
/// <param name="issuer">发行人</param>
/// <param name="audience">订阅人</param>
/// <param name="signingCredentials">签名验证实体</param>
public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
{
ClaimType = claimType;
DeniedAction = deniedAction;
Permissions = permissions;
Issuer = issuer;
Audience = audience;
Expiration = expiration;
SigningCredentials = signingCredentials;
}
}
}
设置Startup.cs
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens; namespace GRPCDemo01Service
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//读取配置文件
var audienceConfig = Configuration.GetSection("Audience");
var symmetricKeyAsBase64 = audienceConfig["Secret"];
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = audienceConfig["Issuer"],//发行人
ValidateAudience = true,
ValidAudience = audienceConfig["Audience"],//订阅人
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
//这个集合模拟用户权限表,可从数据库中查询出来
var permission = new List<Permission> {
new Permission { Url="/Goods.Goodser/GetGoods", Name="admin"},
new Permission { Url="systemapi", Name="system"}
};
//如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
var permissionRequirement = new PermissionRequirement(
"/api/denied", permission,
ClaimTypes.Role,
audienceConfig["Issuer"],
audienceConfig["Audience"],
signingCredentials,
expiration: TimeSpan.FromSeconds()//设置Token过期时间
); services.AddAuthorization(options =>
{
options.AddPolicy("Permission", policy => policy.AddRequirements(permissionRequirement));
}).
AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
{
//不使用https
o.RequireHttpsMetadata = true;
o.TokenValidationParameters = tokenValidationParameters;
});
//注入授权Handler
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddSingleton(permissionRequirement);
services.AddGrpc();
} public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GoodsService>(); endpoints.MapGet("/", async context =>
{
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");
});
});
}
}
}
GoodsService.cs
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Grpc.Core;
using GRPCDemo01Entity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging; namespace GRPCDemo01Service
{
[Authorize("Permission")]
public class GoodsService : Goodser.GoodserBase
{
private readonly ILogger<GoodsService> _logger;
readonly PermissionRequirement _requirement;
public GoodsService(ILogger<GoodsService> logger, PermissionRequirement requirement)
{
_requirement = requirement;
_logger = logger;
}
public override Task<QueryResponse> GetGoods(QueryRequest request, ServerCallContext context)
{
return Task.FromResult(new QueryResponse
{
Name = "Hello " + request.Name,
Quantity =
});
}
[AllowAnonymous]
public override Task<LoginResponse> Login(LoginRequest user, ServerCallContext context)
{
//todo 查询数据库核对用户名密码
var isValidated = user.Username == "gsw" && user.Password == "";
if (!isValidated)
{
return Task.FromResult(new LoginResponse()
{
Message = "认证失败"
});
}
else
{
//如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
var claims = new Claim[] {
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())
}; var token = JwtToken.BuildJwtToken(claims, _requirement);
return Task.FromResult(new LoginResponse()
{
Result = true,
Token = token.access_token
}); }
}
}
}
控制台程序调用gRPC
添加引用 GRPCDemo01Entity项目
安装NuGet包
Grpc.Net.Client
Program.cs
using Grpc.Core;
using Grpc.Net.Client;
using GRPCDemo01Entity;
using System;
using System.Threading.Tasks; namespace GRPCDemo01Test
{
class Program
{
static async Task Main(string[] args)
{
while (true)
{
Console.WriteLine("用户名:");
var username = Console.ReadLine();
Console.WriteLine("密码:");
var password = Console.ReadLine();
var tokenResponse = await Login(username, password);
if (tokenResponse.Result)
{
await Query(tokenResponse.Token);
}
else
{
Console.WriteLine("登录失败");
}
}
}
/// <summary>
/// 查询
/// </summary>
/// <param name="token">token</param>
/// <returns></returns>
static async Task Query(string token)
{
token = $"Bearer {token }";
var headers = new Metadata { { "Authorization", token } };
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Goodser.GoodserClient(channel);
var query = await client.GetGoodsAsync(
new QueryRequest { Name = "桂素伟" }, headers);
Console.WriteLine($"返回值 Name:{ query.Name},Quantity:{ query.Quantity}");
}
/// <summary>
/// 登录
/// </summary>
/// <param name="userName">userName</param>
/// <param name="password">password</param>
/// <returns></returns>
static async Task<LoginResponse> Login(string userName, string password)
{
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Goodser.GoodserClient(channel);
var response = await client.LoginAsync(
new LoginRequest() { Username = userName, Password = password });
return response;
}
}
}
webapi调用gRPC
添加引用 GRPCDemo01Entity项目
安装NuGet包
Grpc.Net.ClientFactory
Starup的ConfigureServices添加如下代码
//添加Grpc客户端
services.AddGrpcClient<Goodser.GoodserClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
});
调用gRPC
using System.Threading.Tasks;
using Grpc.Core;
using GRPCDemo01Entity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; namespace GRPCDemo01WebTest.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
/// <summary>
/// 客户端
/// </summary>
private readonly Goodser.GoodserClient _client;
public WeatherForecastController(ILogger<WeatherForecastController> logger, Goodser.GoodserClient client)
{
_client = client;
_logger = logger;
} [HttpGet]
public async Task<string> Get()
{
//登录
var tokenResponse = await _client.LoginAsync(new LoginRequest { Username = "gsw", Password = "" });
var token = $"Bearer {tokenResponse.Token }";
var headers = new Metadata { { "Authorization", token } };
var request = new QueryRequest { Name = "桂素伟" };
//查询
var query = await _client.GetGoodsAsync(request, headers);
return $"Name:{query.Name},Quantity:{query.Quantity}";
}
}
}
gRPC调用gRPC
添加引用 GRPCDemo01Entity项目
Starup的ConfigureServices添加如下代码
//添加Grpc客户端
services.AddGrpcClient<Goodser.GoodserClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
});
调用gRPC
using System;
using System.Threading.Tasks;
using Grpc.Core;
using GRPCDemo01Entity;
using Microsoft.Extensions.Logging; namespace GRPCDemo01GRPCTest
{
public class OrderService : Orderer.OrdererBase
{
private readonly ILogger<OrderService> _logger;
private readonly Goodser.GoodserClient _client;
public OrderService(ILogger<OrderService> logger, Goodser.GoodserClient client)
{
_client = client;
_logger = logger;
}
public override async Task<OrderResponse> GetGoods(OrderRequest request, ServerCallContext context)
{
//登录
var tokenResponse = await _client.LoginAsync(
new LoginRequest() {
Username = "gsw",
Password = ""
});
if (tokenResponse.Result)
{
var token = $"Bearer {tokenResponse.Token }";
var headers = new Metadata { { "Authorization", token } };
//查询
var query = await _client.GetGoodsAsync(
new QueryRequest { Name = "桂素伟" }, headers);
Console.WriteLine($"返回值 Name:{ query.Name},Quantity:{ query.Quantity}");
return new OrderResponse { Name = query.Name, Quantity = query.Quantity };
}
else
{
Console.WriteLine("登录失败");
return null;
}
}
}
}
gRPC asp.net core自定义策略认证的更多相关文章
- asp.net core 自定义认证方式--请求头认证
asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...
- ASP.NET Core的身份认证框架IdentityServer4--入门
ASP.NET Core的身份认证框架IdentityServer4--入门 2018年08月11日 10:09:00 qq_42606051 阅读数 4002 https://blog.csdn ...
- asp.net core自定义端口
asp.net Core 自定义端口 官方文档 aspnet内库源码: https://github.com/aspnet dotnet系统内库源码:https://github.com/dotnet ...
- asp.net core 2.1认证
asp.net core 2.1认证 这篇文章基于asp.net core的CookieAuthenticationHandler来讲述. 认证和授权很相似,他们的英文也很相似,一个是Authenti ...
- Asp.Net Core基于JWT认证的数据接口网关Demo
近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...
- 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?
原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...
- asp.net core 自定义异常处理中间件
asp.net core 自定义异常处理中间件 Intro 在 asp.net core 中全局异常处理,有时候可能不能满足我们的需要,可能就需要自己自定义一个中间件处理了,最近遇到一个问题,有一些异 ...
- asp.net core 自定义 Policy 替换 AllowAnonymous 的行为
asp.net core 自定义 Policy 替换 AllowAnonymous 的行为 Intro 最近对我们的服务进行了改造,原本内部服务在内部可以匿名调用,现在增加了限制,通过 identit ...
- asp.net core 自定义基于 HttpContext 的 Serilog Enricher
asp.net core 自定义基于 HttpContext 的 Serilog Enricher Intro 通过 HttpContext 我们可以拿到很多有用的信息,比如 Path/QuerySt ...
随机推荐
- Spring容器启动源码解析
1. 前言 最近搭建的工程都是基于SpringBoot,简化配置的感觉真爽.但有个以前的项目还是用SpringMvc写的,看到满满的配置xml文件,却有一种想去深入了解的冲动.折腾了好几天,决心去写这 ...
- CSAPP: 位操作实现基本运算
目录 实验要求 实现代码 1.pow2plus1 2.pow2plus4 3.bitXor 4.tmin 5.isTmax 6.allOddBits 7.negate 8.isAsciiDigit 9 ...
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- 2019.10.29 CSP%您赛第四场t2
我太菜了我竟然不会分层图最短路 ____________________________________________________________________________________ ...
- JVM学习记录1--JVM内存布局
先上个图 这是根据<Java虚拟机规范(第二版)>所画的jvm内存模型. 程序计数器:程序计数器是用来记录当前线程方法执行顺序的,对应的就是我们编程中一行行代码的执行顺序,如分支,跳转,循 ...
- reduce,map,filter 的用法
# filter用法 (对每一个数据进行筛选,满足条件的就保留)# 1,filter(功能函数,容器类型数据)# 2,过滤出来是一个地址,用list,或tuple进行包装# 3,过滤出来的是原容器类型 ...
- Python实战练习——打印日历教程
很长一段时间没有接触过C语言了,想来做这一行当已经有三两年了. 今天突然想起来以前用C语言在VC6上写代码的日子,想了想以前的一些实战练习. 所以今天打算用Python来写一个C语言以前练习的题目-日 ...
- 学习笔记34_EF上下文管理
*上下文对象dbContext最好不要频繁的使用Using(var dbContext = new ....):那么就会产生过多的数据库交互:而且每个dbContext中村的数据,由于操作不同,数据可 ...
- [考试反思]1017csp-s模拟测试77(lrd day1) :反抗
说在前面:强烈谴责AK神Mr_zkt没有丝毫素质RP-- 然而我也想没素质一次,但是我没机会AK一套除了B组题以外的题... 太菜了,没权力.人家AK了人家就是牛逼你没话说 达哥的题必须好好写反思. ...
- CSPS_105
不想多说... T1 是$1$还是$26^n-1$ T2 是$f[getf(u)]=getf(v)$还是$f[u]=v$ T3 是$if(condition1\&\&condition ...