ASP.NET Core 6.0 添加 JWT 认证和授权
序言
本文将分别介绍 Authentication(认证) 和 Authorization(授权)。
并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能。
相关名词
Authentication 和 Authorization 长得很像,傻傻分不清楚。
Authentication(认证):标识用户的身份,一般发生在登录的时候。
Authorization(授权):授予用户权限,指定用户能访问哪些资源;授权的前提是知道这个用户是谁,所以授权必须在认证之后。
认证(Authentication)
基本步骤
- 安装相关 Nuget 包:Microsoft.AspNetCore.Authentication.JwtBearer
- 准备配置信息(密钥等)
- 添加服务
- 调用中间件
- 实现一个 JwtHelper,用于生成 Token
- 控制器限制访问(添加 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
实现 IAuthorizationService
的 AuthorizeAsync
方法。
AuthorizeAsync
方法会获取到所有实现了 IAuthorizationHandler
的实例,并循环调用所有实例的 HandleAsync 方法检查是否满足授权要求,如果有任一一个 HandleAsync
返回了 Fail 则将结束循环(细节请参考官方文档处理程序返回结果),并禁止用户访问。
IAuthorizationHandler
的作用如上一点所述,提供了一个 HandleAsync
方法,用于检查授权。
IAuthorizationRequirement
是一个要求,主要是配合 AuthorizationHandler<TRequirement>
使用。
AuthorizationHandler<TRequirement>
实现了 IAuthorizationHandler
的 HandleAsync
方法,并提供了一个 HandleRequirementAsync
的方法。HandleRequirementAsync
用于检查 Requirement
(要求)是否满足。HandleAsync
的默认实现为获取所有实现 TRequirement
的请求(且该请求由 Policy 添加进列表里),循环调用 HandleRequirementAsync
,检查哪个要求(Requirement)能满足授权。
简述一下:
[Authorize]
标签生效时,调用的是 IAuthorizationService
的 AuthorizeAsync
(由 DefaultAuthorizationService
实现)。
AuthorizeAsync
会去调用所有 IAuthorizationHandler
的 HandleAsync
(由 AuthorizationHandler<TRequirement>
实现)。
HandleAsync
会去调用 AuthorizationHandler<TRequirement>
的HandleRequirementAsync
的方法。
注意:这里只是列出了主要的接口和类,部分没有列出,如:IAuthorizationHandlerProvider
(这个接口的默认实现 DefaultAuthorizationHandlerProvider
,主要是用于收集 IAuthorizationHandler
并返回 IEnumerable<IAuthorizationHandler>
)
2 实现说明
IAuthorizationService
已默认实现,不需要我们做额外工作。
IAuthorizationHandler
由 AuthorizationHandler<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 官方文档:授权策略提供程序
ASP.NET Core 认证与授权6:授权策略是怎么执行的?(mark:这篇很强!)
ASP.NET Core 6.0 添加 JWT 认证和授权的更多相关文章
- ASP.NET Core 3.0 gRPC 身份认证和授权
一.开头聊骚 本文算是对于 ASP.NET Core 3.0 gRPC 研究性学习的最后一篇了,以后在实际使用中,可能会发一些经验之文.本文主要讲 ASP.NET Core 本身的认证授权和gRPC接 ...
- ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token
传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...
- ASP.NET Core 3.0 一个 jwt 的轻量角色/用户、单个API控制的授权认证库
目录 说明 一.定义角色.API.用户 二.添加自定义事件 三.注入授权服务和中间件 三.如何设置API的授权 四.添加登录颁发 Token 五.部分说明 六.验证 说明 ASP.NET Core 3 ...
- ASP.NET Core 2.0使用Cookie认证实现SSO单点登录
之前写了一个使用ASP.NET MVC实现SSO登录的Demo,https://github.com/bidianqing/SSO.Sample,这个Demo是基于.NET Framework,.NE ...
- 【ASP.NET Core学习】使用JWT认证授权
概述 认证授权是很多系统的基本功能 , 在以前PC的时代 , 通常是基于cookies-session这样的方式实现认证授权 , 在那个时候通常系统的用户量都不会很大, 所以这种方式也一直很好运行, ...
- asp.net core 2.0 web api基于JWT自定义策略授权
JWT(json web token)是一种基于json的身份验证机制,流程如下: 通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端 ...
- ASP.NET Core WebAPI中使用JWT Bearer认证和授权
目录 为什么是 JWT Bearer 什么是 JWT JWT 的优缺点 在 WebAPI 中使用 JWT 认证 刷新 Token 使用授权 简单授权 基于固定角色的授权 基于策略的授权 自定义策略授权 ...
- Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录
1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...
- Asp.net Core认证和授权:JWT认证和授权
JWT验证一般用户移动端,因为它不像cookie验证那样,没有授权跳转到登陆页面 JWT是json web token的简称,在 jwt.io 网址可以看到 新建一个API项目,通过postman ...
随机推荐
- [转载]详解ssh端口转发(二)
关于使用ssh portforwarding来进行FQ的操作,网络上已经有很多很好的文章,我在这里只是画两个图解释一下. 首先要记住一件事情就是: SSH 端口转发自然需要 SSH 连接,而 SSH ...
- vue2.x版本中computed和watch的使用入门详解-关联和区别
前面两篇介绍了computed和watch的基本使用 watch篇 computed篇 两者的区别,继续通过代码实现的方式具体去了解 html <li>最开始的value值:{{ name ...
- Dubbo 支持服务降级吗?
以通过 dubbo:reference 中设置 mock="return null".mock 的值也可以修改 为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规 ...
- kafka消费组创建和删除原理
0.10.0.0版本的kafka的消费者和消费组已经不在zk上注册节点了,那么消费组是以什么形式存在的呢? 1 入口 看下kafka自带的脚本kafka-consumer-groups.sh,可见脚本 ...
- 移动端H5页面中1px边框的几种解决方法
问题提出 这是一个比较老的问题了,我第一次注意到的时候,是UI设计师来找我麻烦,emmm那时候我才初入前端职场,啥也不懂啊啊啊啊啊,情形是这样的:设计师拿着手机过来:这些边框都粗了啊,我的设计稿上是1 ...
- 【论文简读】 Deep web data extraction based on visual
<Deep web data extraction based on visual information processing>作者 J Liu 上海海事大学 2017 AIHC会议登载 ...
- Linux 0.11源码阅读笔记-内存管理
内存管理 Linux内核使用段页式内存管理方式. 内存池 物理页:物理空闲内存被划分为固定大小(4k)的页 内存池:所有空闲物理页组成内存池,以页为单位进行分配回收.并通过位图记录了每个物理页是否空闲 ...
- javaweb之连接数据库
最近做完了一个图书系统的增删改查,想着来总结一下这几个月的所学内容. 一.首先你需要在电脑上安装上mysql或者sql server(本文以mysql为例) mysql官网:MySQL :: Begi ...
- tomcat 安装配置及问题解决
最近没写程序 刚想运行一个jsp程序发现tomcat出现一些问题,然后就重新装了程序,重新配置 总结经验就是不要怕报错,把错误复制下来,百度里面都有解决办法 要安装与自己jdk版本相匹配的tomcat ...
- Shiro 安全框架详解一(概念+登录案例实现)
shiro 安全框架详细教程 总结内容 一.RBAC 的概念 二.两种常用的权限管理框架 1. Apache Shiro 2. Spring Security 3. Shiro 和 Spring Se ...