一.前言

  大家好我又回来了,前几天讲过一个关于Jwt的身份验证最简单的案例,但是功能还是不够强大,不适用于真正的项目,是的,在真正面对复杂而又苛刻的客户中,我们会不知所措,就现在需要将认证授权这一块也变的复杂而又实用起来,那在专业术语中就叫做自定义策略的API认证,本次案例运行在.NET Core 3.0中,最后我们将在swagger中进行浏览,来尝试项目是否正常,对于.NET Core 2.x 版本,这篇文章有些代码不适用,但我会在文中说明。

二.在.NET Core中尝试

  我们都知道Jwt是为了认证,微软给我们提供了进城打鬼子的城门,那就是 AuthorizationHandle。

  我们首先要实现它,并且我们还可以根据依赖注入的 AuthorizationHandlerContext 来获取上下文,就这样我们就更可以做一些权限的手脚

public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
var http = (context.Resource as Microsoft.AspNetCore.Routing.RouteEndpoint);
var questUrl = "/"+http.RoutePattern.RawText;
//赋值用户权限
var userPermissions = requirement.UserPermissions;
//是否经过验证
var isAuthenticated = context.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
if (userPermissions.Any(u=>u.Url == questUrl))
{
//用户名
var userName = context.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
if (userPermissions.Any(w => w.UserName == userName))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
}

  首先,我们重写了 HandleRequirementAsync 方法,如果你看过AspNetCore的源码你一定知道,它是Jwt身份认证的开端,也就是说你重写了它,原来那一套就不会走了,我们观察一下源码,我贴在下面,可以看到这就是一个最基本的授权,通过 context.Succeed(requirement 完成了最后的认证动作!

public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>, IAuthorizationRequirement
{
/// <summary>
/// Makes a decision if authorization is allowed based on a specific requirement.
/// </summary>
/// <param name="context">The authorization context.</param>
/// <param name="requirement">The requirement to evaluate.</param>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
{
var user = context.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

那么  Succeed  是一个什么呢?它是一个在  AuthorizationHandlerContext的定义动作,包括Fail() ,也是如此,当然具体实现我们不在细谈,其内部还是挺复杂的,不过我们需要的是  DenyAnonymousAuthorizationRequirement  被当作了抽象的一部分。

public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
where TRequirement : IAuthorizationRequirement
{}

好吧,言归正传(看源码挺刺激的),我们刚刚在  PolicyHandler实现了自定义认证策略,上面还说到了两个方法。现在我们在项目中配置并启动它,并且我在代码中也是用了Swagger用于后面的演示。

在  AddJwtBearer中我们添加了jwt验证包括了验证参数以及几个事件处理,这个很基本,不在解释。不过在Swagger中添加jwt的一些功能是在  AddSecurityDefinition  中写入的。

public void ConfigureServices(IServiceCollection services)
{
//添加策略鉴权模式
services.AddAuthorization(options =>
{
options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement()));
})
.AddAuthentication(s =>
{
//添加JWT Scheme
s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
//添加jwt验证:
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), ValidateAudience = true,//是否验证Audience
//ValidAudience = Const.GetValidudience(),//Audience
//这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
},
ValidateIssuer = true,//是否验证Issuer
ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致 ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "HaoZi JWT",
Description = "基于.NET Core 3.0 的JWT 身份验证",
Contact = new OpenApiContact
{
Name = "zaranet",
Email = "zaranet@163.com",
Url = new Uri("http://cnblogs.com/zaranet"),
},
});
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer Token",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
//认证服务
services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
services.AddControllers();
}

在以上代码中,我们通过鉴权模式添加了认证规则,一个名叫  PolicyRequirement  的类,它实现了  IAuthorizationRequirement  接口,其中我们需要定义一些规则,通过构造函数我们可以添加我们要识别的权限规则。那个UserName就是 Attribute 。

public class PolicyRequirement : IAuthorizationRequirement
{/// <summary>
/// User rights collection
/// </summary>
public List<UserPermission> UserPermissions { get; private set; }
/// <summary>
/// No permission action
/// </summary>
public string DeniedAction { get; set; }
/// <summary>
/// structure
/// </summary>
public PolicyRequirement()
{
//Jump to this route without permission
DeniedAction = new PathString("/api/nopermission");
//Route configuration that users have access to, of course you can read it from the database, you can also put it in Redis for persistence
UserPermissions = new List<UserPermission> {
new UserPermission { Url="/api/value3", UserName="admin"},
};
}
}
public class UserPermission
{
public string UserName { get; set; }
public string Url { get; set; }
}

随后我们应当启动我们的服务,在.NET Core 3.0 中身份验证的中间件位置需要在路由和端点配置的中间。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

  我们通常会有一个获取token的API,用于让Jwt通过  JwtSecurityTokenHandler().WriteToken(token)  用于生成我们的token,虽然jwt是没有状态的,但你应该也明白,如果你的jwt生成了随后你重启了你的网站,你的jwt会失效,这个是因为你的密钥进行了改变,如果你的密钥一直写死,那么这个jwt将不会再过期,这个还是有安全风险的,这个我不在这里解释,gettoken定义如下:

  [ApiController]
public class AuthController : ControllerBase
{
[AllowAnonymous]
[HttpGet]
[Route("api/nopermission")]
public IActionResult NoPermission()
{
return Forbid("No Permission!");
}
/// <summary>
/// login
/// </summary>
[AllowAnonymous]
[HttpGet]
[Route("api/auth")]
public IActionResult Get(string userName, string pwd)
{
if (CheckAccount(userName, pwd, out string role))
{
Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
// push the user’s name into a claim, so we can identify the user later on.
//这里可以随意加入自定义的参数,key可以自己随便起
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.NameIdentifier, userName),
new Claim("Role", role)
};
//sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
var token = new JwtSecurityToken(
issuer: Const.Domain, //颁发者
audience: Const.ValidAudience,//过期时间
expires: DateTime.Now.AddMinutes(30),// 签名证书
signingCredentials: creds, //自定义参数
claims: claims );
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
/// <summary>
/// 模拟登陆校验
/// </summary>
private bool CheckAccount(string userName, string pwd, out string role)
{
role = "user";
if (string.IsNullOrEmpty(userName))
return false;
if (userName.Equals("admin"))
role = "admin";
return true;
}

  可能比较特别的是  AllowAnonymous  ,这个看我文章的同学可能头一次见,其实怎么说好呢,这个可无可有,没有硬性的要求,我看到好几个知名博主加上了,我也加上了~...最后我们创建了几个资源控制器,它们是受保护的。

  在你添加策略权限的时候例如政策名称是XXX,那么在对应的api表头就应该是XXX,随后到了  PolicyHandler我们解析了 Claims 处理了它是否有权限。

// GET api/values1
[HttpGet]
[Route("api/value1")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value1" };
}
// GET api/values2
/**
* 该接口用Authorize特性做了权限校验,如果没有通过权限校验,则http返回状态码为401
*/
[HttpGet]
[Route("api/value2")]
[Authorize]
public ActionResult<IEnumerable<string>> Get2()
{
var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
return new string[] { "这个接口登陆过的都能访问", $"userName={userName}" };
}
/**
* 这个接口必须用admin
**/
[HttpGet]
[Route("api/value3")]
[Authorize("Permission")]
public ActionResult<IEnumerable<string>> Get3()
{
//这是获取自定义参数的方法
var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
var role = auth.FirstOrDefault(t => t.Type.Equals("Role"))?.Value;
return new string[] { "这个接口有管理员权限才可以访问", $"userName={userName}", $"Role={role}" };
}

三.效果图

四.栗子源代码和以往版本

  看到很多前辈彩的坑,原来的  (context.Resource as Microsoft.AspNetCore.Routing.RouteEndpoint);  实际上在.NET Core 3.0 已经不能用了,原因是.NET Core 3.0 启用 EndpointRouting 后,权限filter不再添加到 ActionDescriptor ,而将权限直接作为中间件运行,同时所有filter都会添加到  endpoint.Metadata  ,如果在.NET Core 2.1 & 2.2 版本中你通常Handler可以这么写:

public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
//赋值用户权限
var userPermissions = requirement.UserPermissions;
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
//请求Url
var questUrl = httpContext.Request.Path.Value.ToUpperInvariant();
//是否经过验证
var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
if (userPermissions.GroupBy(g => g.Url).Any(w => w.Key.ToUpperInvariant() == questUrl))
{
//用户名
var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
if (userPermissions.Any(w => w.UserName == userName && w.Url.ToUpperInvariant() == questUrl))
{
context.Succeed(requirement);
}
else
{
//无权限跳转到拒绝页面
httpContext.Response.Redirect(requirement.DeniedAction);
}
}
else
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

  该案例源代码在我的Github上:https://github.com/zaranetCore/aspNetCore_JsonwebToken/tree/master/Jwt_Policy_Demo  谢谢大家

三分钟学会.NET Core Jwt 策略授权认证的更多相关文章

  1. [转]三分钟学会.NET Core Jwt 策略授权认证

    [转]三分钟学会.NET Core Jwt 策略授权认证 一.前言# 大家好我又回来了,前几天讲过一个关于Jwt的身份验证最简单的案例,但是功能还是不够强大,不适用于真正的项目,是的,在真正面对复杂而 ...

  2. 三分钟学会@Autowired@Qualifier@Primary注解

    三分钟学会@Autowired@Qualifier@Primary注解 2018.10.08 20:24 154浏览 今天主要简单的跟大家介绍一下spring自动装配相关的@Autowired,@Qu ...

  3. 三分钟学会使用Derby数据库

    Derby数据库是一个纯用Java实现的内存数据库,属于Apache的一个开源项目.由于是用Java实现的,所以可以在任何平台上运行:另外一个特点是体积小,免安装,java1.6开始集成了derby数 ...

  4. CZGL.Auth: ASP.NET Core Jwt角色授权快速配置库

    CZGL.Auth CZGL.Auth 是一个基于 Jwt 实现的快速角色授权库,ASP.Net Core 的 Identity 默认的授权是 Cookie.而 Jwt 授权只提供了基础实现和接口,需 ...

  5. JWT实现授权认证

    目录 一. JWT是什么 二. JWT标准规范 三. 核心代码简析 四. 登录授权示例 五. JWT 使用方式 六. JWT注意事项 一. JWT是什么 JSON Web Token(JWT)是目前最 ...

  6. 中小研发团队架构实践之生产环境诊断工具WinDbg 三分钟学会.NET微服务之Polly 使用.Net Core+IView+Vue集成上传图片功能 Fiddler原理~知多少? ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) C#程序中设置全局代理(Global Proxy) WCF 4.0 使用说明 如何在IIS上发布,并能正常访问

    中小研发团队架构实践之生产环境诊断工具WinDbg 生产环境偶尔会出现一些异常问题,WinDbg或GDB是解决此类问题的利器.调试工具WinDbg如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具 ...

  7. 三分钟学会在ASP.NET Core MVC 中使用Cookie

    一.Cookie是什么? 我的朋友问我cookie是什么,用来干什么的,可是我居然无法清楚明白简短地向其阐述cookie,这不禁让我陷入了沉思:为什么我无法解释清楚,我对学习的方法产生了怀疑!所以我们 ...

  8. 三分钟学会 ASP.NET Core WebApi使用Swagger生成api说明文档

    什么是Swagger?为啥要用Swagger? Swagger可以从不同的代码中,根据注释生成API信息,Swagger拥有强大的社区,并且对于各种语言都支持良好,有很多的工具可以通过swagger生 ...

  9. 三分钟学会Redis在.NET Core中做缓存中间件

    大家好,今天给大家说明如何在.NET Core中使用Redis,我们在想要辩论程序的好与坏,都想需要一个可视化工具,我经常使用的是一位国内大牛开发的免费工具,其Github地址为: https://g ...

随机推荐

  1. 1163 - Bank Robbery

    1163 - Bank Robbery   In one very cold morning, Mark decides to rob a bank. But while trying hacking ...

  2. 线段树学习----C语言

    /* 线段树学习:如果一个节点为i,那么他的左孩子为2I+1,右孩子为2i+2: */ #include<stdio.h> #define min(a,b) a<b?a:b; ]; ...

  3. React之拆分组件与组件之间的传值

    父子组件传值: 父组件向子组件传值通过向子组件TodoItem进行属性绑定(content={item}.index={index}),代码如下 getTodoItem () { return thi ...

  4. JVM类加载器是否可以加载自定义的String

    前言 曾经有一次,面试官问到类加载机制,相信大多数小伙伴都可以答上来双亲委派机制,也都知道JVM出于安全性的考虑,全限定类名相同的String是不能被加载的.但是如果加载了,会出现什么样的结果呢?异常 ...

  5. Java高级项目实战03:CRM系统数据库设计

    接上一篇:Java高级项目实战02:客户关系管理系统CRM系统模块分析与介绍 欢迎点击回顾,接下来我们说说 CRM系统数据库设计. 我们根据产品的原型搞以及UI组的设计稿, 接下来就要设计数据库, 一 ...

  6. Python股票量化第一步环境搭建

    很久之前就希望可以量化分析股票,那么国内的股票数据API也有个,最有名的就是tushare,然后还有baostock. 今天我们就来研究一下这个baostock吧. 首先,我们需要下载一个叫做anac ...

  7. kali帮助

    kali help Attention 这是我N久前学习kali时自己打的东西,不保证没有纰漏啊…… 网址 kali:https://www.herojd.com/forum.php?mod=view ...

  8. Android进程调度之adj算法

    copy from : http://gityuan.com/2016/08/07/android-adj/ 一.概述 提到进程调度,可能大家首先想到的是Linux cpu调度算法,进程优先级之类概念 ...

  9. No mapping found for HTTP request with URI [/SLSaleSystem/js/jquery.dataTables.min.js] in DispatcherServlet with name 'spring' 静态资源文件访问不到,无解!!!!!!!

    报错信息:   网上三种修改 web.xml 文件方法尝试未果 尝试未果:<mvc:default-servlet-handler/> 尝试未果:方法2:直接告诉spring,这个你就得这 ...

  10. C#_.net core 3.0自定义读取.csv文件数据_解决首行不是标题的问题_Linqtocsv改进

    linqtocsv文件有不太好的地方就是:无法设置标题的行数,默认首行就是标题,这不是很尴尬吗?   并不是所有的csv文件严格写的首行是标题,下面全是数据,我接受的任务就是读取很多.csv报表数据, ...