title: Asp.Net Core权限认证
date: 2022-10-27 16:17:52
tags:
- .NET

翻了很多的博客,文档,发现asp.net core自带的权限认证还是比较复杂的,极少有哪篇文章把整个体系涉及到的知识点都讲清楚的,因此自己整理出了这篇文章,相当于自己的一个个人理解和总结吧

关键概念

认证和授权

asp.net core中将权限认证分成了两个部分,一个是认证(Authentication),一个是授权(Authorization),他们的作用分别是:

  1. Authentication是根据不同方案(Scheme),来校验用户是否通过认证,比如用户名密码是否正确,通过了的话就会调用下面的语句来写入登录信息
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,claimPrincipal);
  1. Authorization是根据规则来判断用户是否通过了认证,通过了认证才授权这个用户访问对应的接口

举一个简单的例子:你们小区出入都有保安在守着,还有门禁,没有权限的人不让进入(接口上使用了Authorize特性),如果你是小区的业主,你得去物业那边登记一下个人资料,物业会记录你的手机号,身份证等个人信息,然后给你颁发一个门禁卡(这一步相当于认证),之后你每次进入小区,都需要刷门禁卡才能进入(授权)

用户角色

用户登录后我们可以通过HttpContext.User访问当前用户的个人信息,其中包含了以下三个重要的概念:

  1. ClaimsPrincipal:当前登录用户的角色
  2. ClaimsIdentity:当前用户持有的证件,比如身份证,银行卡
  3. Claim:证件上的每一个信息,比如身份证上的姓名,出生日期,过期时间,每个都是一个claim

还是接着上面那个例子讲,去物业登记的时候给你颁发的门禁卡,就相当于ClaimsIdentity,上面的每一个信息就是一个Claim,你是ClaimsPrincipal,一个人可以拥有多个证件,比如门禁卡、银行卡、身份证,在进入小区的时候就会通过门禁卡里面的信息判断是否授权给你进去

授权方式

Authorize特性其实已经包含了几种不同的授权方式

  1. 基于角色 Roles
  2. 基于策略 Policy
  3. 基于方案 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

参考链接

  1. https://aspdotnetcore.net/docs/claims-based-authentication/
  2. https://zhuanlan.zhihu.com/p/359691679
  3. https://www.cnblogs.com/diudiu1/p/15818648.html
  4. https://zhuanlan.zhihu.com/p/359691679
  5. https://blog.csdn.net/icoolno1/article/details/108190010
  6. https://www.cnblogs.com/fanfan-90/p/11918537.html
  7. https://www.cnblogs.com/danvic712/p/use-cookie-authentication-in-asp-net-core.html
  8. Cookie刷新有效期
  9. https://zhuanlan.zhihu.com/p/364928893
  10. https://blog.csdn.net/qq_25991955/article/details/100540155
  11. https://www.cnblogs.com/axzxs2001/p/7482777.html
  12. 自定义授权Handler和Requirement
  13. 基于角色授权
  14. https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html#iauthorizationrequirement
  15. asp.net core 6认证示例代码
  16. 微软官方认证授权的文档

Asp-Net-Core权限认证的更多相关文章

  1. asp.net core 自定义认证方式--请求头认证

    asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...

  2. ASP.NET Core Token认证

    翻译:Token Authentication in ASP.NET Core 令牌认证(Token Authentication)已经成为单页应用(SPA)和移动应用事实上的标准.即使是传统的B/S ...

  3. 深入解读 ASP.NET Core 身份认证过程

    长话短说:上文我们讲了 ASP.NET Core 基于声明的访问控制到底是什么鬼? 今天我们乘胜追击:聊一聊ASP.NET Core 中的身份验证. 身份验证是确定用户身份的过程. 授权是确定用户是否 ...

  4. asp.net core 身份认证/权限管理系统简介及简单案例

    如今的网站大多数都离不开账号注册及用户管理,而这些功能就是通常说的身份验证.这些常见功能微软都为我们做了封装,我们只要利用.net core提供的一些工具就可以很方便的搭建适用于大部分应用的权限管理系 ...

  5. ASP.NET Core 身份认证 (Identity、Authentication)

    Authentication和Authorization 每每说到身份验证.认证的时候,总不免说提及一下这2个词.他们的看起来非常的相似,但实际上他们是不一样的. Authentication想要说明 ...

  6. asp.net core权限模块的快速构建

    大部分系统都会有权限模块,别人家系统的权限怎么生成的我不知道,我只知道这样做是可以并且挺好的. 文章中只对asp.net core的部分代码进行说明 呃 记录~,mvc版本自行前往仓库查阅 代码中的一 ...

  7. asp.net core 外部认证多站点模式实现

    PS:之前因为需要扩展了微信和QQ的认证,使得网站是可以使用QQ和微信直接登录.github 传送门 .然后有小伙伴问,能否让这个配置信息(appid, appsecret)按需改变,而不是在 Con ...

  8. .Net Core权限认证基于Cookie的认证&授权.Scheme、Policy扩展

    在身份认证中,如果某个Action需要权限才能访问,最开始的想法就是,哪个Action需要权限才能访问,我们写个特性标注到上面即可,[TypeFilter(typeof(CustomAuthorize ...

  9. ASP.NET Core - JWT认证实现

    一.JWT结构 JWT介绍就太多了,这里主要关注下Jwt的结构. Jwt中包含三个部分:Header(头部).Payload(负载).Signature(签名) Header:描述 JWT 的元数据的 ...

  10. 使用WebApi和Asp.Net Core Identity 认证 Blazor WebAssembly(Blazor客户端应用)

    原文:https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-web ...

随机推荐

  1. SpringBoot 解决跨域问题代码

    package com.example.demo.gs; import org.springframework.context.annotation.Configuration; import jav ...

  2. HPL Study 2

    1.并行编程 (1)并行程序的逻辑: 1)将当前问题划分为多个子任务 2)考虑任务间所需要的通信通道 3)将任务聚合成复合任务 4)将复合任务分配到核上 (2)共享内存编程: 路障 ----> ...

  3. 【网络】内网穿透方案&FRP内网穿透实战(基础版)

    目录 前言 方案 方案1:公网 方案2:第三方内网穿透软件 花生壳 cpolar 方案3:云服务器做反向代理 FRP简介 FRP资源 FRP原理 FRP配置教程之SSH 前期准备 服务器配置 下载FR ...

  4. C#使用正则表达式来验证是否是16进制字符串

    /// <summary> /// 判断是否为16进制字符串 /// </summary> /// <param name="hexString"&g ...

  5. python(牛客)试题解析1 - 简单

    导航: 一.NC103 反转字符串 二.NC141 判断是否为回文字符串 三.NC151 最大公约数 四.NC65 斐波那契数列 五.字符按排序后查看第k个最小的字母 六.数组内取出下标相同的元素求和 ...

  6. c++题目:吃西瓜

    吃西瓜 [问题描述] 老胡买了是长方体形的西瓜来犒劳大家.... 这块西瓜长m厘米,宽n厘米,高h厘米.他发现如果把这块西瓜平均地分成m*n*h块1立方厘米的小正方体,那么每一小块都会有一个营养值(可 ...

  7. VS2019 iis无法在Web服务器上启动调试。打开的URL的IIS辅助进程当前没有运行

    可以检查W3SVC服务与WAS这两个服务是否正在运行. 重启这两个服务就可以正常了

  8. C++期末考试题库

    哈尔滨商业大学计算机专业C++期末考试题库 下载:题库 示例: 一.单选题:1.能作为 C ++程序的基本单位是( C )A .字符 B .语句 C .函数 D .源程序文件2.程序中主函数的名字为( ...

  9. Docker 工作原理分析

    docker 容器原理分析 docker 的工作方式 Namespace 容器对比虚拟机 Cgroups 容器看到的文件 Mount namespace chroot rootfs Volume(数据 ...

  10. NOIP 口胡

    因为没准备啥东西 这两天口胡一下近年 NOIP 的题 大概会一道不落?没什么很寄的考点主要是 2021 T1 报数 打一个 \(O(\log n)\) 查询 \(n\) 中是否有 \(7\),打一个类 ...