序言

本文将分别介绍 Authentication(认证) 和 Authorization(授权)。

并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能。

相关名词

Authentication 和 Authorization 长得很像,傻傻分不清楚。

Authentication(认证):标识用户的身份,一般发生在登录的时候。

Authorization(授权):授予用户权限,指定用户能访问哪些资源;授权的前提是知道这个用户是谁,所以授权必须在认证之后。

认证(Authentication)

基本步骤

  1. 安装相关 Nuget 包:Microsoft.AspNetCore.Authentication.JwtBearer
  2. 准备配置信息(密钥等)
  3. 添加服务
  4. 调用中间件
  5. 实现一个 JwtHelper,用于生成 Token
  6. 控制器限制访问(添加 Authorize 标签)

1 安装 Nuget 包

安装 Microsoft.AspNetCore.Authentication.JwtBearer

在程序包管理器控制台中:

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 6.0.1

2 准备配置信息

在 appsetting.json 中,添加一个 Jwt 节点

"Jwt": {
"SecretKey": "lisheng741@qq.com",
"Issuer": "WebAppIssuer",
"Audience": "WebAppAudience"
}

3 添加服务

在 Program.cs 文件中注册服务。

// 引入所需的命名空间
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text; // ……
var configuration = builder.Configuration; // 注册服务
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true, //是否验证Issuer
ValidIssuer = configuration["Jwt:Issuer"], //发行人Issuer
ValidateAudience = true, //是否验证Audience
ValidAudience = configuration["Jwt:Audience"], //订阅人Audience
ValidateIssuerSigningKey = true, //是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])), //SecurityKey
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
RequireExpirationTime = true,
};
});

4 调用中间件

调用 UseAuthentication(认证),必须在所有需要身份认证的中间件前调用,比如 UseAuthorization(授权)。

// ……
app.UseAuthentication();
app.UseAuthorization();
// ……

5 JwtHelper 类实现

主要是用于生成 JWT 的 Token。

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text; namespace TestWebApi; public class JwtHelper
{
private readonly IConfiguration _configuration; public JwtHelper(IConfiguration configuration)
{
_configuration = configuration;
} public string CreateToken()
{
// 1. 定义需要使用到的Claims
var claims = new[]
{
new Claim(ClaimTypes.Name, "u_admin"), //HttpContext.User.Identity.Name
new Claim(ClaimTypes.Role, "r_admin"), //HttpContext.User.IsInRole("r_admin")
new Claim(JwtRegisteredClaimNames.Jti, "admin"),
new Claim("Username", "Admin"),
new Claim("Name", "超级管理员")
}; // 2. 从 appsettings.json 中读取SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])); // 3. 选择加密算法
var algorithm = SecurityAlgorithms.HmacSha256; // 4. 生成Credentials
var signingCredentials = new SigningCredentials(secretKey, algorithm); // 5. 根据以上,生成token
var jwtSecurityToken = new JwtSecurityToken(
_configuration["Jwt:Issuer"], //Issuer
_configuration["Jwt:Audience"], //Audience
claims, //Claims,
DateTime.Now, //notBefore
DateTime.Now.AddSeconds(30), //expires
signingCredentials //Credentials
); // 6. 将token变为string
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); return token;
}
}

该 JwtHelper 依赖于 IConfiguration(为了读取配置文件),将 JwtHelper 的创建交由 DI 容器,在 Program.cs 中添加服务:

var configuration = builder.Configuration;
builder.Services.AddSingleton(new JwtHelper(configuration));

将 JwtHelper 注册为单例模式。

6 控制器配置

新建一个 AccountController,以构造函数方式注入 JwtHelper,添加两个 Action:GetToken 用于获取 Token,GetTest 打上 [Authorize] 标签用于验证认证。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; namespace TestWebApi.Controllers; [Route("api/[controller]/[action]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly JwtHelper _jwtHelper; public AccountController(JwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
} [HttpGet]
public ActionResult<string> GetToken()
{
return _jwtHelper.CreateToken();
} [Authorize]
[HttpGet]
public ActionResult<string> GetTest()
{
return "Test Authorize";
}
}

7 测试调用

方式一:通过 Postman、Apifox 等接口调试软件调试

使用 Postman 调用 /api/Account/GetToken 生成 Token

在调用 /api/Account/GetTest 时传入 Token,得到返回结果

方式二:在浏览器控制台调试

调试 /api/Account/GetToken

var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
console.log(token = this.responseText); //这里用了一个全局变量 token,为下一个接口服务
}
});
xhr.open("GET", "/api/Account/GetToken");
xhr.send();

调试 /api/Account/GetTest

var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
console.log(this.status, this.responseText); //this.status为响应状态码,401为无认证状态
}
});
xhr.open("GET", "/api/Account/GetTest");
xhr.setRequestHeader("Authorization",`Bearer ${token}`); //附带上 token
xhr.send();

授权(Authorization)

注意:授权必须基于认证,即:若没有完成上文关于认证的配置,则下面的授权是不会成功的。

授权部分,将先介绍相关标签、授权方式,再介绍基于策略的授权。这三部分大致的内容如下描述:

相关标签:Authorize 和 AllowAnonymous

授权方式:介绍 Policy、Role、Scheme 的基本内容

基于策略(Policy)的授权:深入 Policy 授权方式

相关标签(Attribute)

授权相关标签具体请查考官方文档简单授权

[Authorize]

打上该标签的 Controller 或 Action 必须经过认证,且可以标识需要满足哪些授权规则。

授权规则可以是 Policy(策略)、Roles(角色) 或 AuthenticationSchemes(方案)。

[Authorize(Policy = "", Roles ="", AuthenticationSchemes ="")]

[AllowAnonymous]

允许匿名访问,级别高于 [Authorize] ,若两者同时作用,将生效 [AllowAnonymous]

授权方式

基本上授权只有:Policy、Role、Scheme 这3种方式,对应 Authorize 标签的3个属性。

1 Policy(策略)

推荐的授权方式,在 ASP.NET Core 的官方文档提及最多的。一个 Policy 可以包含多个要求(要求可能是 Role 匹配,也可能是 Claims 匹配,也可能是其他方式。)

下面举个基础例子(说是基础例子,主要是基于 Policy 的授权方式可以不断深入追加一些配置):

在 Program.cs 中,添加两条 Policy:

policy1 要求用户拥有一个 Claim,其 ClaimType 值为 EmployeeNumber。

policy2 要求用户拥有一个 Claim,其 ClaimType 值为 EmployeeNumber,且其 ClaimValue 值为1、2、3、4 或 5。

builder.Services.AddAuthorization(options => {
options.AddPolicy("policy1", policy => policy.RequireClaim("EmployeeNumber"));
options.AddPolicy("policy2", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
})

在控制器中添加 [Authorize] 标签即可生效:

[Authorize(Policy = "policy1")]
public class TestController : ControllerBase

或在控制器的 Action 上:

public class TestController : ControllerBase
{
[Authorize(Policy = "policy1")]
public ActionResult<string> GetTest => "GetTest";
}

2 Role(角色)

基于角色授权,只要用户拥有角色,即可通过授权验证。

在认证时,给用户添加角色相关的 Claim ,即可标识用户拥有的角色(注:一个用户可以拥有多个角色的 Claim),如:

new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Role, "user")

在 Controller 或 Action 中:

[Authorize(Roles = "user")]
public class TestController : ControllerBase
{
public ActionResult<string> GetUser => "GetUser"; [Authorize(Roles = "admin")] //与控制器的Authorize叠加作用,除了拥有user,还需拥有admin
public ActionResult<string> GetAdmin => "GetAdmin"; [Authorize(Roles = "user,admin")] //user 或 admin 其一满足即可
public ActionResult<string> GetUserOrAdmin => "GetUserOrAdmin";
}

3 Scheme(方案)

方案如:Cookies 和 Bearer,当然也可以是自定义的方案。

由于这种方式不常用,这里不做展开,请参考官方文档按方案限制标识

基于策略(Policy)的授权

上面已经提及了一个基于策略授权的基础例子,下面将继续深入这种授权方式。

1 授权过程

在不断深入 Policy 这种方式的授权之前,有必要将授权的过程描述一下。授权过程描述建议结合源码查看,这样能更清楚其中的作用。当然,这一部分是比较难懂,笔者表述可能也不够清晰,而这一部分对于完成授权的配置也不会有影响,故而如果读者看不明白或无法理解,可以暂且跳过,不必纠结。

建议看一下ASP.NET Core 认证与授权6:授权策略是怎么执行的?这篇文章,文章将授权相关的源码整理出来了,并说明了其中的关系。

这里简单梳理一下:

与授权相关的 interface 和 class 如下:

IAuthorizationService #验证授权的服务,主要方法AuthorizeAsync
DefaultAuthorizationService #IAuthorizationService的默认实现
IAuthorizationHandler #负责检查是否满足要求,主要方法HandleAsync
IAuthorizationRequirement #只有属性,没有方法;用于标记服务,以及用于追踪授权是否成功的机制。
AuthorizationHandler<TRequirement> #主要方法HandleRequirementAsync

这些 interface 和 class 的关系以及授权过程是这样的:

DefaultAuthorizationService 实现 IAuthorizationServiceAuthorizeAsync 方法。

AuthorizeAsync 方法会获取到所有实现了 IAuthorizationHandler 的实例,并循环调用所有实例的 HandleAsync 方法检查是否满足授权要求,如果有任一一个 HandleAsync 返回了 Fail 则将结束循环(细节请参考官方文档处理程序返回结果),并禁止用户访问。

IAuthorizationHandler 的作用如上一点所述,提供了一个 HandleAsync 方法,用于检查授权。

IAuthorizationRequirement 是一个要求,主要是配合 AuthorizationHandler<TRequirement> 使用。

AuthorizationHandler<TRequirement> 实现了 IAuthorizationHandlerHandleAsync 方法,并提供了一个 HandleRequirementAsync 的方法。HandleRequirementAsync 用于检查 Requirement(要求)是否满足。HandleAsync 的默认实现为获取所有实现 TRequirement 的请求(且该请求由 Policy 添加进列表里),循环调用 HandleRequirementAsync,检查哪个要求(Requirement)能满足授权。

简述一下:

[Authorize] 标签生效时,调用的是 IAuthorizationServiceAuthorizeAsync(由 DefaultAuthorizationService 实现)。

AuthorizeAsync 会去调用所有 IAuthorizationHandlerHandleAsync (由 AuthorizationHandler<TRequirement> 实现)。

HandleAsync 会去调用 AuthorizationHandler<TRequirement>HandleRequirementAsync 的方法。

注意:这里只是列出了主要的接口和类,部分没有列出,如:IAuthorizationHandlerProvider(这个接口的默认实现 DefaultAuthorizationHandlerProvider,主要是用于收集 IAuthorizationHandler 并返回 IEnumerable<IAuthorizationHandler>

2 实现说明

IAuthorizationService 已默认实现,不需要我们做额外工作。

IAuthorizationHandlerAuthorizationHandler<TRequirement> 实现。

所以我们要做的,是:

第一步,准备 Requirement 实现 IAuthorizationRequirement

第二步,添加一个 Handler 程序继承 AuthorizationHandler<TRequirement> 并重写 HandleRequirementAsync 方法

关于具体实现,可以参考ASP.NET Core 认证与授权7:动态授权基于权限的授权部分,该文章思路已十分清晰,这里将其主要步骤列出来。

3 定义权限项

在实现 Requirement 之前,我们需要先定义一些权限项,主要用于后续作为 Policy 的名称,并传入 我们实现的 Requirement 之中。

public static class UserPermission
{
public const string User = "User";
public const string UserCreate = User + ".Create";
public const string UserDelete = User + ".Delete";
public const string UserUpdate = User + ".Update";
}

如上,定义了“增”、“删”、“改”等权限,其中 User 将拥有完整权限。

4 实现 Requirement

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
public PermissionAuthorizationRequirement(string name)
{
Name = name;
}
public string Name { get; set; }
}

使用 Name 属性表示权限的名称,与 UserPermission 中的常量对应。

5 实现授权处理程序 Handler

这里假定用户的 Claim 中 ClaimType 为 Permission 的项,如:

new Claim("Permission", UserPermission.UserCreate),
new Claim("Permission", UserPermission.UserUpdate)

如上,标识该用户用户 UserCreate 和 UserUpdate 的权限。

注意:当然,实际程序我们肯定不是这样实现的,这里只是简易示例。

接着,实现一个授权 Handler:

public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement)
{
var permissions = context.User.Claims.Where(_ => _.Type == "Permission").Select(_ => _.Value).ToList();
if (permissions.Any(_ => _.StartsWith(requirement.Name)))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

运行 HandleRequirementAsync 时,会将用户的 Claim 中 ClaimType 为 Permission 的项取出,并获取其 Value 组成一个 List<string>

接着验证 Requirement 是否满足授权,满足则运行 context.Succeed

6 添加授权处理程序

在 Program.cs 中,将 PermissionAuthorizationHandler 添加到 DI 中:

builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

7 添加授权策略

builder.Services.AddAuthorization(options =>
{
options.AddPolicy(UserPermission.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserCreate)));
options.AddPolicy(UserPermission.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserUpdate)));
options.AddPolicy(UserPermission.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserDelete)));
});

8 控制器配置

控制器如下:

[Route("api/[controller]/[action]")]
[ApiController]
public class UserController : ControllerBase
{
[HttpGet]
[Authorize(UserPermission.UserCreate)]
public ActionResult<string> UserCreate() => "UserCreate"; [HttpGet]
[Authorize(UserPermission.UserUpdate)]
public ActionResult<string> UserUpdate() => "UserUpdate"; [HttpGet]
[Authorize(UserPermission.UserDelete)]
public ActionResult<string> UserDelete() => "UserDelete";
}

基于上面的假定,用户访问接口的情况如下:

/api/User/UserCreate #成功
/api/User/UserUpdate #成功
/api/User/UserDelete #403无权限

至此,基于策略(Policy)的授权其实已经基本完成

接下去的内容,将是对上面一些内容的完善或补充。

完善:实现策略提供程序 PolicyProvider

一般添加授权策略如下是在 Program.cs 中,方式如下:

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("policy", policy => policy.RequireClaim("EmployeeNumber"));
});

通过 AuthorizationOptions.AddPolicy 添加授权策略这种方式不灵活,无法动态添加。

通过实现 IAuthorizationPolicyProvider 并添加到 DI 中,可以实现动态添加 Policy。

IAuthorizationPolicyProvider 的默认实现为 DefaultAuthorizationPolicyProvider

实现一个 PolicyProvider 如下:

public class TestAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, IAuthorizationPolicyProvider
{
public Test2AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) {} public new Task<AuthorizationPolicy> GetDefaultPolicyAsync()
=> return base.GetDefaultPolicyAsync(); public new Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
return base.GetFallbackPolicyAsync(); public new Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(UserPermission.User))
{
var policy = new AuthorizationPolicyBuilder("Bearer");
policy.AddRequirements(new PermissionAuthorizationRequirement(policyName));
return Task.FromResult<AuthorizationPolicy?>(policy.Build());
}
return base.GetPolicyAsync(policyName);
}
}

注意:自定义的 TestAuthorizationPolicyProvider 必须实现 IAuthorizationPolicyProvider,否则添加到 DI 时会不生效。

在 Program.cs 中,将自定义的 PolicyProvider 添加到 DI 中:

builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthorizationPolicyProvider>();

注意:只会生效最后一个添加的 PolicyProvider。

补充:自定义 AuthorizationMiddleware

自定义 AuthorizationMiddleware 可以:

  • 返回自定义的响应
  • 增强(或者说改变)默认的 challenge 或 forbid 响应

具体请查看官方文档自定义 AuthorizationMiddleware 的行为

补充:MiniApi 的授权

在 MiniApi 中几乎都是形如 MapGet() 的分支节点,这类终结点无法使用 [Authorize] 标签,可以用使用 RequireAuthorization("Something") 进行授权,如:

app.MapGet("/helloworld", () => "Hello World!")
.RequireAuthorization("AtLeast21");

参考来源

ASP.NET Core 6.0 官方文档:授权策略提供程序

.NET 6 使用JWT Bearer认证和授权的步骤

ASP.NET Core 认证与授权6:授权策略是怎么执行的?(mark:这篇很强!)

ASP.NET Core 认证与授权7:动态授权

ASP.NET Core 6.0 添加 JWT 认证和授权的更多相关文章

  1. ASP.NET Core 3.0 gRPC 身份认证和授权

    一.开头聊骚 本文算是对于 ASP.NET Core 3.0 gRPC 研究性学习的最后一篇了,以后在实际使用中,可能会发一些经验之文.本文主要讲 ASP.NET Core 本身的认证授权和gRPC接 ...

  2. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...

  3. ASP.NET Core 3.0 一个 jwt 的轻量角色/用户、单个API控制的授权认证库

    目录 说明 一.定义角色.API.用户 二.添加自定义事件 三.注入授权服务和中间件 三.如何设置API的授权 四.添加登录颁发 Token 五.部分说明 六.验证 说明 ASP.NET Core 3 ...

  4. ASP.NET Core 2.0使用Cookie认证实现SSO单点登录

    之前写了一个使用ASP.NET MVC实现SSO登录的Demo,https://github.com/bidianqing/SSO.Sample,这个Demo是基于.NET Framework,.NE ...

  5. 【ASP.NET Core学习】使用JWT认证授权

    概述 认证授权是很多系统的基本功能 , 在以前PC的时代 , 通常是基于cookies-session这样的方式实现认证授权 , 在那个时候通常系统的用户量都不会很大, 所以这种方式也一直很好运行, ...

  6. asp.net core 2.0 web api基于JWT自定义策略授权

    JWT(json web token)是一种基于json的身份验证机制,流程如下: 通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端 ...

  7. ASP.NET Core WebAPI中使用JWT Bearer认证和授权

    目录 为什么是 JWT Bearer 什么是 JWT JWT 的优缺点 在 WebAPI 中使用 JWT 认证 刷新 Token 使用授权 简单授权 基于固定角色的授权 基于策略的授权 自定义策略授权 ...

  8. Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录

    1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...

  9. Asp.net Core认证和授权:JWT认证和授权

    JWT验证一般用户移动端,因为它不像cookie验证那样,没有授权跳转到登陆页面 JWT是json web token的简称,在  jwt.io 网址可以看到 新建一个API项目,通过postman ...

随机推荐

  1. [转载]详解ssh端口转发(二)

    关于使用ssh portforwarding来进行FQ的操作,网络上已经有很多很好的文章,我在这里只是画两个图解释一下. 首先要记住一件事情就是: SSH 端口转发自然需要 SSH 连接,而 SSH ...

  2. vue2.x版本中computed和watch的使用入门详解-关联和区别

    前面两篇介绍了computed和watch的基本使用 watch篇 computed篇 两者的区别,继续通过代码实现的方式具体去了解 html <li>最开始的value值:{{ name ...

  3. Dubbo 支持服务降级吗?

    以通过 dubbo:reference 中设置 mock="return null".mock 的值也可以修改 为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规 ...

  4. kafka消费组创建和删除原理

    0.10.0.0版本的kafka的消费者和消费组已经不在zk上注册节点了,那么消费组是以什么形式存在的呢? 1 入口 看下kafka自带的脚本kafka-consumer-groups.sh,可见脚本 ...

  5. 移动端H5页面中1px边框的几种解决方法

    问题提出 这是一个比较老的问题了,我第一次注意到的时候,是UI设计师来找我麻烦,emmm那时候我才初入前端职场,啥也不懂啊啊啊啊啊,情形是这样的:设计师拿着手机过来:这些边框都粗了啊,我的设计稿上是1 ...

  6. 【论文简读】 Deep web data extraction based on visual

    <Deep web data extraction based on visual information processing>作者 J Liu 上海海事大学 2017 AIHC会议登载 ...

  7. Linux 0.11源码阅读笔记-内存管理

    内存管理 Linux内核使用段页式内存管理方式. 内存池 物理页:物理空闲内存被划分为固定大小(4k)的页 内存池:所有空闲物理页组成内存池,以页为单位进行分配回收.并通过位图记录了每个物理页是否空闲 ...

  8. javaweb之连接数据库

    最近做完了一个图书系统的增删改查,想着来总结一下这几个月的所学内容. 一.首先你需要在电脑上安装上mysql或者sql server(本文以mysql为例) mysql官网:MySQL :: Begi ...

  9. tomcat 安装配置及问题解决

    最近没写程序 刚想运行一个jsp程序发现tomcat出现一些问题,然后就重新装了程序,重新配置 总结经验就是不要怕报错,把错误复制下来,百度里面都有解决办法 要安装与自己jdk版本相匹配的tomcat ...

  10. Shiro 安全框架详解一(概念+登录案例实现)

    shiro 安全框架详细教程 总结内容 一.RBAC 的概念 二.两种常用的权限管理框架 1. Apache Shiro 2. Spring Security 3. Shiro 和 Spring Se ...