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 ...
随机推荐
- Java编程基础——敬请期待!!!
变量 数据类型 条件判断 循环 函数 类 Java特性
- jsp和java的结合使用显示学生信息
package com.zyz; public class Student { private String ID; // 学号 private String name; // 姓名 private ...
- Vue学习之--------列表排序(ffilter、sort、indexOf方法的使用)、Vue检测数据变化的原理(2022/7/15)
文章目录 1.列表排序 1.1 .代码实例 1.2 .测试效果 1.3.需要掌握的前提知识 2.Vue监测数据变化的原理 2.1.代码实例 2.2 .测试效果 3.Vue检测数据的原理 3.1 基本知 ...
- CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别
CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别 总结: "value 是完整单词" 类型的比较符号: ~=, |= "拼接字符串" 类型的比较 ...
- Java中math类的常用函数
Java中math类的常用函数 在 Java 中 Math 类封装了常用的数学运算,提供了基本的数学操作,如指数.对数.平方根和三角函数等 只要在源文件的顶部加上下面这行代码就不必在数学方法名和常量名 ...
- 2022NISACTF--WEB
easyssrf 打开题目,显示的是 尝试输入, 发现输入flag有东西 读取文件 访问下一个网站 读取文件 不能以file开头 直接伪协议,base64解码 checkIn 奇怪的unicode编码 ...
- 第2-1-1章 FastDFS分布式文件服务背景及系统架构介绍
目录 1 背景 1.1 为什么需要分布式文件服务 1.1.1 单机时代 1.1.2 独立文件服务器 1.1.3 分布式文件系统 1.2 什么是FastDFS 2 系统架构 2.1 Tracker集群 ...
- 长事务 (Long Transactions)
长事务 长事务用于支持 AutoCAD 参照编辑功能,对于 ObjectARX 应用程序非常有用.这些类和函数为应用程序提供了一种方案,用于签出实体以进行编辑并将其签回其原始位置.此操作会将原始对象替 ...
- 为什么 softmax 计算时要先减去最大值
根据 softmax 最基本的定义,计算公式如下所示: $$S_i=\frac{e^{x_i}}{\sum_j e^{x_j}}$$ 原理也很简单,将原向量变为分布的形式(和为1). 看似很美好,但是 ...
- 使用SVN搭建本地版本控制仓库
使用SVN搭载本地版本控制仓库[转] 如果是在公司,都是有云服务器,项目负责人都是把项目放在服务器上,我们直接用SVN地址就可以实现更新和下载项目源码,那么如果我们自己想使用SVN在本机管理自己写的一 ...