Asp-Net-Core权限认证
title: Asp.Net Core权限认证
date: 2022-10-27 16:17:52
tags:
- .NET
翻了很多的博客,文档,发现asp.net core自带的权限认证还是比较复杂的,极少有哪篇文章把整个体系涉及到的知识点都讲清楚的,因此自己整理出了这篇文章,相当于自己的一个个人理解和总结吧
关键概念
认证和授权
asp.net core中将权限认证分成了两个部分,一个是认证(Authentication),一个是授权(Authorization),他们的作用分别是:
- Authentication是根据不同方案(Scheme),来校验用户是否通过认证,比如用户名密码是否正确,通过了的话就会调用下面的语句来写入登录信息
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,claimPrincipal);
- Authorization是根据规则来判断用户是否通过了认证,通过了认证才授权这个用户访问对应的接口
举一个简单的例子:你们小区出入都有保安在守着,还有门禁,没有权限的人不让进入(接口上使用了Authorize特性),如果你是小区的业主,你得去物业那边登记一下个人资料,物业会记录你的手机号,身份证等个人信息,然后给你颁发一个门禁卡(这一步相当于认证),之后你每次进入小区,都需要刷门禁卡才能进入(授权)
用户角色
用户登录后我们可以通过HttpContext.User访问当前用户的个人信息,其中包含了以下三个重要的概念:
ClaimsPrincipal:当前登录用户的角色ClaimsIdentity:当前用户持有的证件,比如身份证,银行卡Claim:证件上的每一个信息,比如身份证上的姓名,出生日期,过期时间,每个都是一个claim
还是接着上面那个例子讲,去物业登记的时候给你颁发的门禁卡,就相当于
ClaimsIdentity,上面的每一个信息就是一个Claim,你是ClaimsPrincipal,一个人可以拥有多个证件,比如门禁卡、银行卡、身份证,在进入小区的时候就会通过门禁卡里面的信息判断是否授权给你进去
授权方式
Authorize特性其实已经包含了几种不同的授权方式
- 基于角色 Roles
- 基于策略 Policy
- 基于方案 Scheme
还是上面那个例子,进入小区的时候必须得刷门禁卡,刷身份证、银行卡肯定是进不去的,为什么呢?这就好比小区门禁的授权采用的是基于方案Scheme的,认证方式是物业认证,认证通过之后给你颁发了一个门禁卡,进入小区的时候根据物业指定的方案来验证颁发的门禁卡是否符合授权的要求
实操案例
新建一个Webapi项目,首先我们需要安装一个jwt的nuget包:
Microsoft.AspNetCore.Authentication.JwtBearer
首先我们需要添加认证的服务:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddJwtBearer(
JwtBearerDefaults.AuthenticationScheme,
opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "benji",
ValidAudience = "benji",
// 这里是签名秘钥
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("miyaomiyao12312312312312312312")),
// 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt =>
{
// opt.Cookie
opt.Cookie.Name = "MyCookie";
opt.ExpireTimeSpan = TimeSpan.FromMinutes(10);
opt.Events.OnRedirectToLogin = context =>
{
context.Response.Headers["Location"] = context.RedirectUri;
// 认证失败返回401 否则返回的是404
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
}).AddScheme<AuthenticationSchemeOptions, CustomAuthHandler>(CustomAuthHandler.SchemeName, it => { });
这里一共添加了三种认证方案,一种是基于Jwt的,一种是基于Cookie的,还有一种是我们自定义的认证方案,并且第一行就设置了默认的认证方案是Cookie认证
自定义认证方案需要继承AuthenticationHandler<AuthenticationSchemeOptions>类,下面是示例代码:
public class CustomAuthHandler:AuthenticationHandler<AuthenticationSchemeOptions>
{
public CustomAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
public const string SchemeName= "自定义验证";
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var auth=Request.Headers["Authorization"].ToString();
if (auth == "自定义验证条件")
{
//验证成功后创建用户信息
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "testUser"),
new Claim(ClaimTypes.Role, "testRole")
}, SchemeName);
var principal = new ClaimsPrincipal(claimsIdentity);
var ticket = new AuthenticationTicket(principal, this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
else
{
return AuthenticateResult.Fail("验证失败");
}
}
}
认证方案添加后,我们需要在管道内添加认证和授权的中间件:
app.UseAuthentication();
app.UseAuthorization();
添加完成后就可以使用了,我们先给三种认证授权写对应的登录接口,从而颁发对应的token:
[HttpPost]
public IActionResult LoginByJwt(string username,string password)
{
if (username == "test" && password == "test")
{
//JWT载荷(Payload)
var key = Encoding.ASCII.GetBytes("miyaomiyao12312312312312312312");
var authTime = DateTime.UtcNow;
var expiresAt = authTime.AddDays(7);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = "benji",
Audience = "benji",
//自定义内容
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name,"local"),
new Claim(ClaimTypes.Sid,"123456"),
new Claim("随便定义一个字段","字段对应的值"),
new Claim(ClaimTypes.Role,"admin"),
new Claim(ClaimTypes.Role,"user"),
new Claim(ClaimTypes.Role,"superadmin"),
}),
//过期时间
Expires = expiresAt,
//签证
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
access_token = tokenString,
token_type = "Bearer"
});
}
else
{
return BadRequest("账号错误");
}
}
[HttpPost]
public async Task<IActionResult> LoginByCookie(string username, string password)
{
if (username == "test" && password == "test")
{
//1.创建cookie 保存用户信息,使用claim。将序列化用户信息并将其存储在cookie中
var claims = new List<Claim>()
{
new Claim(ClaimTypes.MobilePhone,"123"),
new Claim(ClaimTypes.Name,"test"),
new Claim(ClaimTypes.Role,"admin"),
new Claim("Id","123"),
new Claim(ClaimTypes.Role,"user"),
new Claim(ClaimTypes.Role,"superadmin"),
};
//2.创建声明主题 指定认证方式 这里使用cookie
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
//3.配置认证属性 比如过期时间,是否持久化。。。。
var authProperties = new AuthenticationProperties
{
// ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
// 是否持久化,类似于前端勾选记住密码
//IsPersistent = true,
//IssuedUtc = <DateTimeOffset>,
};
//4.登录
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
return Ok();
}
else
{
return BadRequest("账号密码错误");
}
}
这里只提供了Jwt和Cookie的创建token的方案,自定义认证方案的话可以根据自己的需求来定制怎么颁发这个token,想弄在header上或者添加一个cookie都行,下面我们就可以对其进行授权测试了:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestCookie()
{
List<string> res = new List<string>();
foreach (var claim in HttpContext.User.Claims)
{
res.Add( claim.Type + "-" + claim.Value);
}
return res;
}
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestJwt()
{
List<string> res = new List<string>();
foreach (var claim in HttpContext.User.Claims)
{
res.Add( claim.Type + "-" + claim.Value);
}
return res;
}
[Authorize(AuthenticationSchemes = CustomAuthHandler.SchemeName)]
[HttpGet]
public List<string> TestCustom()
{
List<string> res = new List<string>();
foreach (var claim in HttpContext.User.Claims)
{
res.Add( claim.Type + "-" + claim.Value);
}
return res;
}
这是针对三种不同认证方案的对应授权策略的写法,其中基于Cookie认证的授权方案可以不用写AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme,因为默认的就是这个,这里就相当于上面说的基于Scheme方案的授权
下面我们再介绍一下基于角色和基于策略的授权,我们先添加一些自定义的授权策略:
builder.Services.AddAuthorization(options =>
{
//基于角色组的策略
options.AddPolicy("管理员", policy =>
{
policy.RequireRole("admin", "system");
// 三种认证结果的证件都算进来
policy.AuthenticationSchemes=new []{ JwtBearerDefaults.AuthenticationScheme,CustomAuthHandler.SchemeName,CookieAuthenticationDefaults.AuthenticationScheme};
});
//基于用户名
options.AddPolicy("用户名是张三", policy => policy.RequireUserName("张三"));
// 基于ClaimType
options.AddPolicy("地址是中国", policy => policy.RequireClaim(ClaimTypes.Country,"中国"));
//自定义值
options.AddPolicy("自定义Claim要求", policy => policy.RequireClaim("date","2017-09-02"));
});
上面都是基于策略的授权,每个策略都可以使用不同认证方案颁发的证件,如果不写的话就是只能使用默认方案的认证结果证件,当然,这里也可以自定义自己的策略,只需要继承IAuthorizationRequirement接口就行了,如下:
/// <summary>
/// 最小年龄限制
/// </summary>
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(int age)
{
MinimumAge = age;
}
public int MinimumAge { get; set; }
}
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(
c => c.Type == ClaimTypes.DateOfBirth );
if (dateOfBirthClaim is null)
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
MinimumAgeRequirement只需要包含我们需要处理的一些信息,当然不写也行,主要是这个Handler才是核心,在这里我们要写对应的校验逻辑,上面这个示例是微软官方文档中的用户最小年龄判断,接下来我们需要添加对应的服务和策略:
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
options.AddPolicy("自定义策略", policy =>
{
policy.Requirements.Add(new MinimumAgeRequirement(1));
});
注意,如果同一个Requirement有多个对应的handler,那么都会执行,只要一个校验的结果是fail,那么整个都是fail,然后在我们颁发token时,需要添加对应的字段:
new Claim(ClaimTypes.DateOfBirth,DateTime.Now.AddYears(-200).ToString()),
接着我们写一个对应的权限校验接口:
[Authorize(Policy = "自定义策略",AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestCustomPolicy()
{
List<string> res = new List<string>();
foreach (var claim in HttpContext.User.Claims)
{
res.Add( claim.Type + "-" + claim.Value);
}
return res;
}
那么基于策略的授权方案这里就介绍完了,下面介绍一下基于角色的授权方案,这种方案比较简单,就是判断认证的证件里面的Role这个Claim是否有自己要求的角色,比如:
[Authorize(Roles = "admin",AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestRole()
{
List<string> res = new List<string>();
foreach (var claim in HttpContext.User.Claims)
{
res.Add( claim.Type + "-" + claim.Value);
}
return res;
}
在颁发token的时候一定要加对应的角色:
new Claim(ClaimTypes.Role,"admin"),
new Claim(ClaimTypes.Role,"user"),
new Claim(ClaimTypes.Role,"superadmin"),
角色可以有多个,授权的时候也可以判断多个:
[Authorize(Roles = "admin,user")]
不过直接使用这种基于角色的授权有个问题,就是必须得指定使用某个认证方案,否则使用的是默认的认证方案给出的证件(也有可能是有但是我没找到对应的方法),所以如果需要针对所有的认证方案都进行收集并判断是否有某个角色,可以直接使用基于策略的授权方法,比如上面写过的:
options.AddPolicy("管理员", policy =>
{
policy.RequireRole("admin", "system");
// 三种认证的结果都算进来
policy.AuthenticationSchemes=new []{ JwtBearerDefaults.AuthenticationScheme,CustomAuthHandler.SchemeName,CookieAuthenticationDefaults.AuthenticationScheme};
});
总结
上面基本把所有认证授权相关的内容都介绍完了,还有一些更细节的地方,比如默认的认证方案可以根据条件动态选择、Token的过期刷新、自定义AuthorizeAttribute特性来定义自己的授权方法,这些就属于细枝末节的东西了,可以在后面的参考链接中找到官方文档、博客自行查阅
博客中所有的示例代码可以在这下载: https://github.com/li-zheng-hao/AspNetCore.AuthDemo
参考链接
- https://aspdotnetcore.net/docs/claims-based-authentication/
- https://zhuanlan.zhihu.com/p/359691679
- https://www.cnblogs.com/diudiu1/p/15818648.html
- https://zhuanlan.zhihu.com/p/359691679
- https://blog.csdn.net/icoolno1/article/details/108190010
- https://www.cnblogs.com/fanfan-90/p/11918537.html
- https://www.cnblogs.com/danvic712/p/use-cookie-authentication-in-asp-net-core.html
- Cookie刷新有效期
- https://zhuanlan.zhihu.com/p/364928893
- https://blog.csdn.net/qq_25991955/article/details/100540155
- https://www.cnblogs.com/axzxs2001/p/7482777.html
- 自定义授权Handler和Requirement
- 基于角色授权
- https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html#iauthorizationrequirement
- asp.net core 6认证示例代码
- 微软官方认证授权的文档
Asp-Net-Core权限认证的更多相关文章
- asp.net core 自定义认证方式--请求头认证
asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...
- ASP.NET Core Token认证
翻译:Token Authentication in ASP.NET Core 令牌认证(Token Authentication)已经成为单页应用(SPA)和移动应用事实上的标准.即使是传统的B/S ...
- 深入解读 ASP.NET Core 身份认证过程
长话短说:上文我们讲了 ASP.NET Core 基于声明的访问控制到底是什么鬼? 今天我们乘胜追击:聊一聊ASP.NET Core 中的身份验证. 身份验证是确定用户身份的过程. 授权是确定用户是否 ...
- asp.net core 身份认证/权限管理系统简介及简单案例
如今的网站大多数都离不开账号注册及用户管理,而这些功能就是通常说的身份验证.这些常见功能微软都为我们做了封装,我们只要利用.net core提供的一些工具就可以很方便的搭建适用于大部分应用的权限管理系 ...
- ASP.NET Core 身份认证 (Identity、Authentication)
Authentication和Authorization 每每说到身份验证.认证的时候,总不免说提及一下这2个词.他们的看起来非常的相似,但实际上他们是不一样的. Authentication想要说明 ...
- asp.net core权限模块的快速构建
大部分系统都会有权限模块,别人家系统的权限怎么生成的我不知道,我只知道这样做是可以并且挺好的. 文章中只对asp.net core的部分代码进行说明 呃 记录~,mvc版本自行前往仓库查阅 代码中的一 ...
- asp.net core 外部认证多站点模式实现
PS:之前因为需要扩展了微信和QQ的认证,使得网站是可以使用QQ和微信直接登录.github 传送门 .然后有小伙伴问,能否让这个配置信息(appid, appsecret)按需改变,而不是在 Con ...
- .Net Core权限认证基于Cookie的认证&授权.Scheme、Policy扩展
在身份认证中,如果某个Action需要权限才能访问,最开始的想法就是,哪个Action需要权限才能访问,我们写个特性标注到上面即可,[TypeFilter(typeof(CustomAuthorize ...
- ASP.NET Core - JWT认证实现
一.JWT结构 JWT介绍就太多了,这里主要关注下Jwt的结构. Jwt中包含三个部分:Header(头部).Payload(负载).Signature(签名) Header:描述 JWT 的元数据的 ...
- 使用WebApi和Asp.Net Core Identity 认证 Blazor WebAssembly(Blazor客户端应用)
原文:https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-web ...
随机推荐
- 我的Vue之旅 06 超详细、仿 itch.io 主页设计(Mobile)
第二期 · 使用 Vue 3.1 + TypeScript + Router + Tailwind.css 仿 itch.io 平台主页. 我的主题 HapiGames 是仿 itch.io 的 in ...
- NOI2017蚯蚓排队
原题链接 发现 k<=50 ,在插入和删除时最多会影响不超过 k2 个串,用链表实现插入和删除,然后只需用哈希表维护每个长度不超过k的串的出现次数,哈希的话可以先用比较大的范围的值处理冲突,再映 ...
- How to get the return value of the setTimeout inner function in js All In One
How to get the return value of the setTimeout inner function in js All In One 在 js 中如何获取 setTimeout ...
- GO编译时不避免引入外部动态库的解决方法
简介 最近碰到一个问题,有一个流量采集的组件中使用到了github.com/google/gopacket 这个库,这个库使用一切正常,但是唯独有一个缺点,编译后的二进制文件依赖于libpcap.so ...
- Linux基础_6_文本编辑
vi i #编辑 ESC+:wq #保存退出 ESC+ZZ #保存退出 ESC+:q! #不保存退出 shift+z+q #不保存退出 dd #删除所在行 ESC+u #撤销dd误操作 :/字符串 # ...
- Windows7下驱动开发与调试体系构建——3.调试体系概述
目录/参考资料:https://www.cnblogs.com/railgunRG/p/14412321.html 调试体系概述 0.什么是自建调试体系? 就是复写windows的调试api,使得调试 ...
- day04-JavaScript01
JavaScript01 官方文档 http://www.w3school.com.cn/js/index.asp 基本说明: JavaScript能改变html内容,能改变html属性,能改变htm ...
- 基于PCIe的多路视频采集与显示子系统
基于PCIe的多路视频采集与显示子系统 1 概述 视频采集与显示子系统可以实时采集多路视频信号,并存储到视频采集队列中,借助高效的硬实时视频帧出入队列管理和PCIe C2H DMA引擎, ...
- JS 可编辑表格的实现(进阶)
1.前言 在普通的可编辑表格的基础上,改进可编辑表格.数据来自外部的json(模拟服务端),通过json数据生成可编辑表格.根据实际情况,表格没有新增数据功能.表格的可编辑列,计算的列,每列的数据大小 ...
- 部署redis集群
1.redis部署 redis单实例部署参考:https://www.cnblogs.com/silgen/p/16537299.html 版本:6.2.7 集群:6个节点(redis集群至少3个节点 ...