C#技术栈入门到精通系列19——鉴权授权IdentityServer JWT

2022/4/9 22:19:30

本文主要是介绍C#技术栈入门到精通系列19——鉴权授权IdentityServer JWT,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

阅读目录

1、介绍
2、对比session登录
3、应用案例
  3.1、启用无状态登录系统
  3.2、使用token访问受保护的action
  3.3、给用户添加角色和权限
  3.4、完成注册过程
  3.5、完成登录过程
  3.6、自定义用户模型,添加初始化数据
4、参考

返回系列文章目录 

 

案例代码下载

1、介绍

   IdentityServer JWT是一套鉴权授权开源组件,授权是允许你做什么?即授予权力,鉴权是在程序执行过程中确认你是谁?被允许做什么?IdentityServer JWT遵循JWT(JSON Web Token)这个开放标准。JWT它定义了一种紧凑独立的方式,可以将各方之间的信息作为JSON对象进行安全传输。该信息可以验证和信任,因为是经过数字签名的。JWT由头部Header、有效载荷Payload和签名Signature三部分构成。各个部分见用“.”隔开,可以在网站 https://jwt.io/ 进行详细的跟踪测试。

  头部Header由typ和alg两部分构成,typ表示token的类型,这里固定为JWT,alg表示使用hash算法,例如HMAC SHA256或者RSA。例如编码前Header“{"alg":"HS256","typ":"JWT"}”,编码后“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9”。

  有效载荷Payload作用①存储需要传递的信息,如用户ID、用户名等,②存储元素据,如过期时间、发布人等,③与Header不同,Payload可以加密。例如编码前“{"userId":"BigBox777"}”,base64编码后“eyJ1c2VySWQiOiJCaWdCb3g3NzcifQ==”,base64Url编码后“eyJ1c2VySWQiOiJCaWdCb3g3NzcifQ”。

  签名Signature作用对Header和Payload部分进行签名,保证Token在传输的过程中没有被篡改或者损坏,Signature算法:Signature=HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)。

 

最后我们来看一看JWT标准所定义常用有效载荷字段:

  • iss:该 jwt 的签发者
  • sub:该 jwt 所面向的用户
  • aud:接收该 jwt 的一方
  • nbf:定义在什么时间之前该jwt是不可用的
  • iat(issued at签发):jwt的签发时间,是一个 unix 时间戳
  • exp(expires过期):jwt的过期时长,一个时间长度单位ms
  • jti:jwt的唯一标识,主要用作一次性token,避免重放攻击

 

2、对比session登录

  除了JWT之外还有一种鉴权授权方式session,这是一种有状态的登录方式,而JWT是无状态登录,常用来做单点登录系统(开源免费有OpenAM、OpenIDM、OpenDJ,企业付费有ForgeRock、Microsoft AM),下面看一下两种方式原理对比。

 

Session对比JWT优势
1:相比JWT,服务器可以主动清除session,可以管理授权有效期。
2:session保存在服务器端,相对比较安全,JWT保存在客户端不安全,可以使用HTTPS传输解决。
3:结合Cookie使用,较为灵活,兼容性好。

Session对比JWT劣势
1:Cookie+session在跨域场景表现得并不太好,Cookie跨域不好。
2:如果是分布式部署,需要做多机共享session机制,额外资源投入。
3:基于Cookie的机制很容易被CSRF(跨站请求伪造攻击)。
4:查询session信息可能有数据库查询操作,额外的资源投入。

相关概念
1:session主要存放在服务器,相对安全。
2:Cookie主要存放在客户端,不是很安全。
3:sessionStorage仅在当前会话下有效,关闭页面或浏览器后被清除。
4:localStorage除非被清除,否则被永久保存。

 

3、应用案例

  此案例使用一个NET5创建的ASP.NET Core MVC项目上做演示,先创建一个项目。
3.1、启用无状态登录系统

第一步:在项目文件夹 Controllers 下创建一个控制器 AuthenticateController 。

第二步:给这个控制器加上登录的Action Login ,分别加上Post请求特性 [HttpPost] 和允许匿名访问特性 [AllowAnonymous] //允许匿名访问 ,

第三步:在项目文件夹 Models 下创建登录数据DTO LoginDto.cs 。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 
 6 namespace Demo19_IdentityServerJWT.Models
 7 {
 8     public class LoginDto
 9     {
10         public string Username { get; set; }
11         public string Password { get; set; }
12     }
13 }
LoginDto.cs

第四步:给项目安装JWT框架 Microsoft.AspNetCore.Authentication.JwtBearer v5.0.15 ,这里需要主要NET6使用的是v6.0.0以上的框架。

第五步:在 Login Action中完成创建jwt,并响应客户端200 OK,并返回jwt(注意密码有最小长度要求,太短会报IDX10653异常),完整的 AuthenticateController.cs 代码如下。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 using Microsoft.IdentityModel.Tokens;
 5 using System;
 6 using System.Collections.Generic;
 7 using System.IdentityModel.Tokens.Jwt;
 8 using System.Linq;
 9 using System.Security.Claims;
10 using System.Text;
11 using System.Threading.Tasks;
12 
13 namespace Demo19_IdentityServerJWT.Controllers
14 {
15     public class AuthenticateController : Controller
16     {
17         private readonly string _secretKey = "jwtTestsMiMa777888999";//jwt加密用的密码
18         [AllowAnonymous] //允许匿名访问
19         [HttpPost]
20         public IActionResult Login([FromBody]LoginDto loginDto)
21         {
22             //1、验证用户名和密码
23             //2、创建jwt(header、payload、signiture)
24             //2.1、创建header
25             var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
26             //2.2、创建payload
27             var claims = new Claim[]
28             {
29                 new Claim(JwtRegisteredClaimNames.Sub,"BigBox777") //自定义数据部分,这里使用JWT标准定义的Sub表示该 jwt 所面向的用户
30             };
31             //2.3、创建signiture
32             var secretByte = Encoding.UTF8.GetBytes(_secretKey);
33             var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
34             var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
35             //2.4、创建jwt
36             var token = new JwtSecurityToken(
37                 issuer: "donesoft.cn",//谁发布的
38                 audience: "donesoft.cn",//发布给谁用
39                 claims,//payload数据
40                 notBefore:DateTime.UtcNow,//发布时间
41                 expires:DateTime.UtcNow.AddMinutes(10),//有效期10分钟
42                 signingCredentials //数字签名
43                 );
44             var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
45             //3、返回200 OK,回传jwt
46             return Ok(tokenString);
47         }
48     }
49 }
AuthenticateController.cs

第六步:使用测试工具Postman来发起请求,测试一下是否正常运行。

 

 

 第七步:复制返货的jwt到网站 https://jwt.io/ ,并输入密码进行验证。

3.2、使用token访问受保护的action

  要使用token访问受保护的action,就需要先做系统级的jwt配置,看下面操作。

第一步:由于很多地方需要共用jwt密码,发布者这些信息,我们先把这部分放到配置文件 appsettings.json 里。

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft": "Warning",
 6       "Microsoft.Hosting.Lifetime": "Information"
 7     }
 8   },
 9   "AllowedHosts": "*",
10   "Authentication": {
11     "secretKey": "jwtTestsMiMa777888999", //jwt密码
12     "issuer": "donesoft.cn",  //发布者
13     "audience": "donesoft.cn"  //发布给谁用
14   }
15 }
appsettings.json

第二步:更新项目文件夹 Controllers 下创建一个控制器 AuthenticateController.cs文件,加入对配置文件的依赖注入。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 using Microsoft.Extensions.Configuration;
 5 using Microsoft.IdentityModel.Tokens;
 6 using System;
 7 using System.Collections.Generic;
 8 using System.IdentityModel.Tokens.Jwt;
 9 using System.Linq;
10 using System.Security.Claims;
11 using System.Text;
12 using System.Threading.Tasks;
13 
14 namespace Demo19_IdentityServerJWT.Controllers
15 {
16     public class AuthenticateController : Controller
17     {
18         private readonly IConfiguration _configuration;//配置文件
19         public AuthenticateController(IConfiguration configuration)
20         {
21             _configuration = configuration;
22         }
23         
24         [AllowAnonymous] //允许匿名访问
25         [HttpPost]
26         public IActionResult Login([FromBody]LoginDto loginDto)
27         {
28             //1、验证用户名和密码
29             //2、创建jwt(header、payload、signiture)
30             //2.1、创建header
31             var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
32             //2.2、创建payload
33             var claims = new Claim[]
34             {
35                 new Claim(JwtRegisteredClaimNames.Sub,"BigBox777") //自定义数据部分,这里使用JWT标准定义的Sub表示该 jwt 所面向的用户
36             };
37             //2.3、创建signiture
38             var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:secretKey"]);
39             var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
40             var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
41             //2.4、创建jwt
42             var token = new JwtSecurityToken(
43                 issuer: _configuration["Authentication:issuer"],//谁发布的
44                 audience: _configuration["Authentication:audience"],//发布给谁用
45                 claims,//payload数据
46                 notBefore:DateTime.UtcNow,//发布时间
47                 expires:DateTime.UtcNow.AddMinutes(10),//有效期10分钟
48                 signingCredentials //数字签名
49                 );
50             var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
51             //3、返回200 OK,回传jwt
52             return Ok(tokenString);
53         }
54     }
55 }
AuthenticateController.cs

第三步:在项目Startup.cs文件下的 public void ConfigureServices(IServiceCollection services) 方法内注册jwt服务。

 1 public void ConfigureServices(IServiceCollection services)
 2         {
 3             services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).//使用jwtBearer注册身份认真服务
 4                 AddJwtBearer(options =>//配置jwtBearer
 5                 {
 6                     var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:secretKey"]);
 7                     options.TokenValidationParameters = new TokenValidationParameters()
 8                     {
 9                         //只有配置的发布者donesoft.cn才会被接受
10                         ValidateIssuer = true,
11                         ValidIssuer = Configuration["Authentication:issuer"],
12                         //只有配置的使用者donesoft.cn才会被接受
13                         ValidateAudience = true,
14                         ValidAudience = Configuration["Authentication:audience"],
15                         //验证token是否过期
16                         ValidateLifetime=true,
17                         //对密码进行加密
18                         IssuerSigningKey=new SymmetricSecurityKey(secretByte)
19                     };
20                 });
21             services.AddControllersWithViews(); 
22         }
public void ConfigureServices(IServiceCollection services)

第四步:在项目Startup.cs文件下的 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 方法内配置授权鉴权中间件,需要特别注意中间件顺序

 1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 2         {
 3             if (env.IsDevelopment())
 4             {
 5                 app.UseDeveloperExceptionPage();
 6             }
 7             else
 8             {
 9                 app.UseExceptionHandler("/Home/Error");
10             }
11             app.UseStaticFiles();
12             //使用路由,访问去哪里?
13             app.UseRouting();
14             //验证中间件,你有什么权限?
15             app.UseAuthentication();
16             //授权中间件,你可以干什么?
17             app.UseAuthorization();  
18 
19             app.UseEndpoints(endpoints =>
20             {
21                 endpoints.MapControllerRoute(
22                     name: "default",
23                     pattern: "{controller=Home}/{action=Index}/{id?}");
24             });
25         }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

第五步:文件夹 Controllers 下创建一个控制器 BookController.cs 文件,再创建一个POST的Action CreateNewBook 用来测试业务逻辑。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 using System;
 5 using System.Collections.Generic;
 6 using System.Linq;
 7 using System.Threading.Tasks;
 8 
 9 namespace Demo19_IdentityServerJWT.Controllers
10 {
11     public class BookController : Controller
12     {
13         [HttpPost]
14         [Authorize]
15         public IActionResult CreateNewBook([FromBody]BookDto bookDto)
16         {
17             return Ok($"{bookDto.BookName}:创建成功!");
18         }
19     }
20 }
BookController.cs
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 
 6 namespace Demo19_IdentityServerJWT.Models
 7 {
 8     public class BookDto
 9     {
10         public string BookName { get; set; }
11         public decimal UnitPrice { get; set; }//单价
12     }
13 }
Models下DTO数据BookDto.cs

第六步:使用Postman测试一下在没有登录的时候,访问返回401 Unauthorized(没有授权)。

 

第七步:我们在Postman请求的头Header里加入登录返回的token,注意添加的字段为 Authorization ,对应值为 bearer  +登录返回的token,这里要严格注意大小写格式和bearer和token之间有空格。

 

第八步:以上就完成了使用token访问授权的action,下一节我们来介绍一下对角色和权限的管理。


3.3、给用户添加角色和权限

  在.NET中基于Claims的身份认证体系(JWT只是Claim的其中一种认证方式),需要用一个类 Claim ①用来表示资源的所有权,例如:说明用户的角色,表示用户所具有的权限。②最小不可分割单位,可以自由组合使用的灵活度相当高。

第一步:更新项目文件夹 Controllers 下控制器 AuthenticateController.cs 文件,给jwt中加入角色信息,关键代码 new Claim(ClaimTypes.Role,"Admin") 。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 using Microsoft.Extensions.Configuration;
 5 using Microsoft.IdentityModel.Tokens;
 6 using System;
 7 using System.Collections.Generic;
 8 using System.IdentityModel.Tokens.Jwt;
 9 using System.Linq;
10 using System.Security.Claims;
11 using System.Text;
12 using System.Threading.Tasks;
13 
14 namespace Demo19_IdentityServerJWT.Controllers
15 {
16     public class AuthenticateController : Controller
17     {
18         private readonly IConfiguration _configuration;//配置文件
19         public AuthenticateController(IConfiguration configuration)
20         {
21             _configuration = configuration;
22         }
23         
24         [AllowAnonymous] //允许匿名访问
25         [HttpPost]
26         public IActionResult Login([FromBody]LoginDto loginDto)
27         {
28             //1、验证用户名和密码
29             //2、创建jwt(header、payload、signiture)
30             //2.1、创建header
31             var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
32             //2.2、创建payload
33             var claims = new Claim[]
34             {
35                 new Claim(JwtRegisteredClaimNames.Sub,"BigBox777"), //自定义数据部分,这里使用JWT标准定义的Sub表示该 jwt 所面向的用户
36                 new Claim(ClaimTypes.Role,"Admin") //加入角色认证信息
37             };
38             //2.3、创建signiture
39             var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:secretKey"]);
40             var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
41             var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
42             //2.4、创建jwt
43             var token = new JwtSecurityToken(
44                 issuer: _configuration["Authentication:issuer"],//谁发布的
45                 audience: _configuration["Authentication:audience"],//发布给谁用
46                 claims,//payload数据
47                 notBefore:DateTime.UtcNow,//发布时间
48                 expires:DateTime.UtcNow.AddMinutes(10),//有效期10分钟
49                 signingCredentials //数字签名
50                 );
51             var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
52             //3、返回200 OK,回传jwt
53             return Ok(tokenString);
54         }
55     }
56 }
AuthenticateController.cs

第二步:更新文件夹 Controllers 下控制器 BookController.cs 文件,指定访问action的角色名称,关键代码 [Authorize(Roles = "Admin")] 。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 using System;
 5 using System.Collections.Generic;
 6 using System.Linq;
 7 using System.Threading.Tasks;
 8 
 9 namespace Demo19_IdentityServerJWT.Controllers
10 {
11     public class BookController : Controller
12     {
13         [HttpPost]
14         [Authorize(Roles = "Admin")]
15         public IActionResult CreateNewBook([FromBody]BookDto bookDto)
16         {
17             return  Created("donesoft.cn",bookDto);
18         }
19     }
20 }
BookController.cs

第三步:完成以上就完成了角色和权限功能。

 

3.4、完成注册过程

  这节来完成注册过程,注册需要用到数据库,这里用到了EFCore,跟着下面一起操作吧。

第一步:nuget安装 Microsoft.AspNetCore.Identity.EntityFrameworkCore v5.0.15 。

第二步:项目下创建Database文件夹,在创建一个数据库上下文AppDbContext.cs来管理数据库数据。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Identity;
 3 using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
 4 using Microsoft.EntityFrameworkCore;
 5 using System;
 6 using System.Collections.Generic;
 7 using System.Linq;
 8 using System.Threading.Tasks;
 9 
10 namespace Demo19_IdentityServerJWT.Database
11 {
12     public class AppDbContext : IdentityDbContext<IdentityUser>
13     {
14         public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
15         {
16         }
17         public DbSet<BookDto> BookDtos { get; set; }
18     }
19 }
AppDbContext.cs
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations;
 4 using System.ComponentModel.DataAnnotations.Schema;
 5 using System.Linq;
 6 using System.Threading.Tasks;
 7 
 8 namespace Demo19_IdentityServerJWT.Models
 9 {
10     public class BookDto
11     {
12         [Key]
13         public int Id { get; set; }
14         [Required]
15         [MaxLength(100)]
16         public string BookName { get; set; }
17         [Required]
18         [Column(TypeName = "decimal(18, 2)")]
19         public decimal UnitPrice { get; set; }//单价
20         [Range(0.0, 1.0)]
21         public double? DiscountPresent { get; set; }//折扣
22         public DateTime CreateTime { get; set; }
23         public DateTime? UpdateTime { get; set; }
24     }
25 }
BookDto.cs

第三步:更新项目下文件Startup.cs里 public void ConfigureServices(IServiceCollection services) 方法里代码,注册身份认证服务框架依赖,关键代码 services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<AppDbContext>();//注册身份认证服务框架依赖 。

 1 using Demo19_IdentityServerJWT.Database;
 2 using Microsoft.AspNetCore.Authentication.JwtBearer;
 3 using Microsoft.AspNetCore.Builder;
 4 using Microsoft.AspNetCore.Hosting;
 5 using Microsoft.AspNetCore.Identity;
 6 using Microsoft.Extensions.Configuration;
 7 using Microsoft.Extensions.DependencyInjection;
 8 using Microsoft.Extensions.Hosting;
 9 using Microsoft.IdentityModel.Tokens;
10 using System;
11 using System.Collections.Generic;
12 using System.Linq;
13 using System.Text;
14 using System.Threading.Tasks;
15 
16 namespace Demo19_IdentityServerJWT
17 {
18     public class Startup
19     {
20         public Startup(IConfiguration configuration)
21         {
22             Configuration = configuration;
23         }
24 
25         public IConfiguration Configuration { get; }
26 
27         // This method gets called by the runtime. Use this method to add services to the container.
28         public void ConfigureServices(IServiceCollection services)
29         {
30             services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<AppDbContext>();//注册身份认证服务框架依赖
31             services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).//使用jwtBearer注册身份认真服务
32                 AddJwtBearer(options =>//配置jwtBearer
33                 {
34                     var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:secretKey"]);
35                     options.TokenValidationParameters = new TokenValidationParameters()
36                     {
37                         //只有配置的发布者donesoft.cn才会被接受
38                         ValidateIssuer = true,
39                         ValidIssuer = Configuration["Authentication:issuer"],
40                         //只有配置的使用者donesoft.cn才会被接受
41                         ValidateAudience = true,
42                         ValidAudience = Configuration["Authentication:audience"],
43                         //验证token是否过期
44                         ValidateLifetime=true,
45                         //对密码进行加密
46                         IssuerSigningKey=new SymmetricSecurityKey(secretByte)
47                     };
48                 });
49             services.AddControllersWithViews(); 
50         }
51 
52         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
53         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
54         {
55             if (env.IsDevelopment())
56             {
57                 app.UseDeveloperExceptionPage();
58             }
59             else
60             {
61                 app.UseExceptionHandler("/Home/Error");
62             }
63             app.UseStaticFiles();
64             //使用路由,访问去哪里?
65             app.UseRouting();
66             //验证中间件,你有什么权限?
67             app.UseAuthentication();
68             //授权中间件,你可以干什么?
69             app.UseAuthorization();  
70 
71             app.UseEndpoints(endpoints =>
72             {
73                 endpoints.MapControllerRoute(
74                     name: "default",
75                     pattern: "{controller=Home}/{action=Index}/{id?}");
76             });
77         }
78     }
79 }
Startup.cs

第四步:打开Visual Studio菜单栏视图=》SQL Server对象资源管理器,创建一个测试数据库TestDB,打开TestDB的属性,找到连接字符串保存到配置文件 appsettings.json 中。

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft": "Warning",
 6       "Microsoft.Hosting.Lifetime": "Information"
 7     }
 8   },
 9   "AllowedHosts": "*",
10   "Authentication": {
11     "secretKey": "jwtTestsMiMa777888999", //jwt密码
12     "issuer": "donesoft.cn", //发布者
13     "audience": "donesoft.cn" //发布给谁用
14   },
15   "DbContext": {
16     "ConnectionString": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=TestDB;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
17   }
18 }
appsettings.json

第五步:nuget安装EFCore需要的迁移工具 Microsoft.EntityFrameworkCore.Tools v5.0.15 、sqlServer包  Microsoft.EntityFrameworkCore v5.0.15 和  Microsoft.EntityFrameworkCore.SqlServer v5.0.15 。在更新Startup.cs里 public void ConfigureServices(IServiceCollection services) 方法里代码,添加EF Core的使用 services.AddDbContext<AppDbContext>(options => { options.UseSqlServer(Configuration["DbContext:ConnectionString"]); }); 。

 1 using Demo19_IdentityServerJWT.Database;
 2 using Microsoft.AspNetCore.Authentication.JwtBearer;
 3 using Microsoft.AspNetCore.Builder;
 4 using Microsoft.AspNetCore.Hosting;
 5 using Microsoft.AspNetCore.Identity;
 6 using Microsoft.EntityFrameworkCore;
 7 using Microsoft.Extensions.Configuration;
 8 using Microsoft.Extensions.DependencyInjection;
 9 using Microsoft.Extensions.Hosting;
10 using Microsoft.IdentityModel.Tokens;
11 using System;
12 using System.Collections.Generic;
13 using System.Linq;
14 using System.Text;
15 using System.Threading.Tasks;
16 
17 namespace Demo19_IdentityServerJWT
18 {
19     public class Startup
20     {
21         public Startup(IConfiguration configuration)
22         {
23             Configuration = configuration;
24         }
25 
26         public IConfiguration Configuration { get; }
27 
28         // This method gets called by the runtime. Use this method to add services to the container.
29         public void ConfigureServices(IServiceCollection services)
30         {
31             services.AddDbContext<AppDbContext>(options => {
32                 options.UseSqlServer(Configuration["DbContext:ConnectionString"]);
33             });
34             services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<AppDbContext>();//注册身份认证服务框架依赖
35             services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).//使用jwtBearer注册身份认真服务
36                 AddJwtBearer(options =>//配置jwtBearer
37                 {
38                     var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:secretKey"]);
39                     options.TokenValidationParameters = new TokenValidationParameters()
40                     {
41                         //只有配置的发布者donesoft.cn才会被接受
42                         ValidateIssuer = true,
43                         ValidIssuer = Configuration["Authentication:issuer"],
44                         //只有配置的使用者donesoft.cn才会被接受
45                         ValidateAudience = true,
46                         ValidAudience = Configuration["Authentication:audience"],
47                         //验证token是否过期
48                         ValidateLifetime=true,
49                         //对密码进行加密
50                         IssuerSigningKey=new SymmetricSecurityKey(secretByte)
51                     };
52                 });
53             services.AddControllersWithViews(); 
54         }
55 
56         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
57         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
58         {
59             if (env.IsDevelopment())
60             {
61                 app.UseDeveloperExceptionPage();
62             }
63             else
64             {
65                 app.UseExceptionHandler("/Home/Error");
66             }
67             app.UseStaticFiles();
68             //使用路由,访问去哪里?
69             app.UseRouting();
70             //验证中间件,你有什么权限?
71             app.UseAuthentication();
72             //授权中间件,你可以干什么?
73             app.UseAuthorization();  
74 
75             app.UseEndpoints(endpoints =>
76             {
77                 endpoints.MapControllerRoute(
78                     name: "default",
79                     pattern: "{controller=Home}/{action=Index}/{id?}");
80             });
81         }
82     }
83 }
Startup.cs

第六步:打开Visual Studio菜单工具=》Nuget包管理器=》程序包管理器控制台 中输入命令 EntityFrameworkCore\Enable-Migrations 打开迁移,再用命令 EntityFrameworkCore\Add-Migration InitDB 和 EntityFrameworkCore\update-database 更新Identity EFCore相关的角色权限数据表到数据库中(打开数据库就可以看见刚刚迁移的数据表)。不少很清楚EF的迁移可以看我的文章https://www.cnblogs.com/bigbox777/p/13907142.html。

  

 

第七步:更新项目文件夹 Controllers 下控制器 AuthenticateController.cs 文件,添加注册用户action函数,这里需要依赖注入用户工具类 private readonly UserManager<IdentityUser> _userManager; 。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Identity;
 4 using Microsoft.AspNetCore.Mvc;
 5 using Microsoft.Extensions.Configuration;
 6 using Microsoft.IdentityModel.Tokens;
 7 using System;
 8 using System.Collections.Generic;
 9 using System.IdentityModel.Tokens.Jwt;
10 using System.Linq;
11 using System.Security.Claims;
12 using System.Text;
13 using System.Threading.Tasks;
14 
15 namespace Demo19_IdentityServerJWT.Controllers
16 {
17     public class AuthenticateController : ControllerBase
18     {
19         private readonly IConfiguration _configuration;//配置文件
20         private readonly UserManager<IdentityUser> _userManager; //依赖注入的用户工具
21         public AuthenticateController(IConfiguration configuration, UserManager<IdentityUser> userManager)
22         {
23             _configuration = configuration;
24             _userManager = userManager;
25         }
26         
27         [AllowAnonymous] //允许匿名访问
28         [HttpPost]
29         public IActionResult Login([FromBody] LoginDto loginDto)
30         {
31             //1、验证用户名和密码
32             //2、创建jwt(header、payload、signiture)
33             //2.1、创建header
34             var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
35             //2.2、创建payload
36             var claims = new Claim[]
37             {
38                 new Claim(JwtRegisteredClaimNames.Sub,"BigBox777"), //自定义数据部分,这里使用JWT标准定义的Sub表示该 jwt 所面向的用户
39                 new Claim(ClaimTypes.Role,"Admin") //加入角色认证信息
40             };
41             //2.3、创建signiture
42             var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:secretKey"]);
43             var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
44             var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
45             //2.4、创建jwt
46             var token = new JwtSecurityToken(
47                 issuer: _configuration["Authentication:issuer"],//谁发布的
48                 audience: _configuration["Authentication:audience"],//发布给谁用
49                 claims,//payload数据
50                 notBefore:DateTime.UtcNow,//发布时间
51                 expires:DateTime.UtcNow.AddMinutes(10),//有效期10分钟
52                 signingCredentials //数字签名
53                 );
54             var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
55             //3、返回200 OK,回传jwt
56             return Ok(tokenString);
57         }
58 
59         [AllowAnonymous] //允许匿名访问
60         [HttpPost]
61         public async Task<IActionResult> Register([FromBody] RegisterDto registerDto) //需要访问数据库用异步操作
62         {
63             //1、使用registerDto对象创建IdentityUser用户对象
64             var user = new IdentityUser()
65             {
66                 UserName = registerDto.Username
67             };
68 
69             //2、使用UserManager来把密码hash加密,保存到数据库
70             var result = await _userManager.CreateAsync(user, registerDto.Password);
71             if (!result.Succeeded)
72             {
73                 return BadRequest();
74             }
75             else
76             {
77                 //3、返回200
78                 return Ok();
79             }
80         }
81     }
82 }
AuthenticateController.cs

第八步:注入还需要用到 RegisterDto.cs ,这用到了特性来比较密码是否一致 [Compare(nameof(Password), ErrorMessage ="密码输入不一致")] //比较两个字段是否一致 。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace Demo19_IdentityServerJWT.Models
 8 {
 9     public class RegisterDto
10     {
11 
12         [Required]
13         public string Username { get; set; }
14         [Required]
15         public string Password { get; set; }
16         [Required]
17         [Compare(nameof(Password), ErrorMessage ="密码输入不一致")] //比较两个字段是否一致
18         public string ConfirmPassword { get; set; }
19     }
20 }
RegisterDto.cs

第九步:以上就完成了注册功能,看下面测试结果。

 

3.5、完成登录过程

第一步:登录的过程还需要用到SignInManager这个签名工具类,更新项目文件夹 Controllers 下控制器 AuthenticateController.cs 文件,添加SignInManager的依赖注入和更新Action函数Login,添加登录验证的逻辑。

  1 using Demo19_IdentityServerJWT.Models;
  2 using Microsoft.AspNetCore.Authorization;
  3 using Microsoft.AspNetCore.Identity;
  4 using Microsoft.AspNetCore.Mvc;
  5 using Microsoft.Extensions.Configuration;
  6 using Microsoft.IdentityModel.Tokens;
  7 using System;
  8 using System.Collections.Generic;
  9 using System.IdentityModel.Tokens.Jwt;
 10 using System.Linq;
 11 using System.Security.Claims;
 12 using System.Text;
 13 using System.Threading.Tasks;
 14 
 15 namespace Demo19_IdentityServerJWT.Controllers
 16 {
 17     public class AuthenticateController : ControllerBase
 18     {
 19         private readonly IConfiguration _configuration;//配置文件
 20         private readonly UserManager<IdentityUser> _userManager; //依赖注入的用户工具
 21         private readonly SignInManager<IdentityUser> _signInManager;//依赖注入签名用户工具
 22         public AuthenticateController(IConfiguration configuration, 
 23             UserManager<IdentityUser> userManager,
 24             SignInManager<IdentityUser> signInManager)
 25         {
 26             _configuration = configuration;
 27             _userManager = userManager;
 28             _signInManager = signInManager;
 29         }
 30         
 31         [AllowAnonymous] //允许匿名访问
 32         [HttpPost]
 33         public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
 34         {
 35             //1、验证用户名和密码
 36             var loginResult = await _signInManager.PasswordSignInAsync(
 37                 loginDto.Username,//用户名
 38                 loginDto.Password,//密码
 39                 false,//关闭浏览器后登录 cookie 是否应保持不变
 40                 false//登录失败时是否锁定用户帐户
 41                 );
 42             if (!loginResult.Succeeded)
 43             {
 44                 return BadRequest();
 45             }
 46             //从数据库中取得用户数据
 47             var user = await _userManager.FindByNameAsync(loginDto.Username);
 48             //2、创建jwt的header
 49             var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
 50             //3、创建jwt的payload
 51             var claims = new List<Claim>()
 52             {
 53                 new Claim(JwtRegisteredClaimNames.Sub,user.Id)
 54             };
 55             //获取用户所有角色,把角色加入payload数据中
 56             var roleNames = await _userManager.GetRolesAsync(user);
 57             foreach (var roleName in roleNames)
 58             {
 59                 var roleClaim = new Claim(ClaimTypes.Role, roleName);
 60                 claims.Add(roleClaim);
 61             }
 62             //4、创建jwt的signiture
 63             var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:secretKey"]);
 64             var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
 65             var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
 66             //5、创建jwt
 67             var token = new JwtSecurityToken(
 68                 issuer: _configuration["Authentication:issuer"],//谁发布的
 69                 audience: _configuration["Authentication:audience"],//发布给谁用
 70                 claims,//payload数据
 71                 notBefore: DateTime.UtcNow,//发布时间
 72                 expires: DateTime.UtcNow.AddMinutes(10),//有效期10分钟
 73                 signingCredentials //数字签名
 74                 );
 75             var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
 76             //6、返回200 OK,回传jwt
 77             return Ok(tokenString);
 78 
 79             #region Login之前测试代码
 80             ////2、创建jwt(header、payload、signiture)
 81             ////2.1、创建header
 82             //var signingAlgorithm = SecurityAlgorithms.HmacSha256;//存储使用算法
 83             ////2.2、创建payload
 84             //var claims = new Claim[]
 85             //{
 86             //    new Claim(JwtRegisteredClaimNames.Sub,"BigBox777"), //自定义数据部分,这里使用JWT标准定义的Sub表示该 jwt 所面向的用户
 87             //    new Claim(ClaimTypes.Role,"Admin") //加入角色认证信息
 88             //};
 89             ////2.3、创建signiture
 90             //var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:secretKey"]);
 91             //var signingKey = new SymmetricSecurityKey(secretByte);//对密码进行加密
 92             //var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //验证加密后的私钥
 93             ////2.4、创建jwt
 94             //var token = new JwtSecurityToken(
 95             //    issuer: _configuration["Authentication:issuer"],//谁发布的
 96             //    audience: _configuration["Authentication:audience"],//发布给谁用
 97             //    claims,//payload数据
 98             //    notBefore: DateTime.UtcNow,//发布时间
 99             //    expires: DateTime.UtcNow.AddMinutes(10),//有效期10分钟
100             //    signingCredentials //数字签名
101             //    );
102             //var tokenString = new JwtSecurityTokenHandler().WriteToken(token);//转成字符串token
103             ////3、返回200 OK,回传jwt
104             //return Ok(tokenString); 
105             #endregion
106         }
107 
108         [AllowAnonymous] //允许匿名访问
109         [HttpPost]
110         public async Task<IActionResult> Register([FromBody] RegisterDto registerDto) //需要访问数据库用异步操作
111         {
112             //1、使用registerDto对象创建IdentityUser用户对象
113             var user = new IdentityUser()
114             {
115                 UserName = registerDto.Username
116             };
117 
118             //2、使用UserManager来把密码hash加密,保存到数据库
119             var result = await _userManager.CreateAsync(user, registerDto.Password);
120             if (!result.Succeeded)
121             {
122                 return BadRequest();
123             }
124             else
125             {
126                 //3、返回200
127                 return Ok();
128             }
129         }
130     }
131 }
AuthenticateController.cs

第二步:给需要验证的Action都加上IdentityServer的Bearer验证,关键代码 [Authorize(AuthenticationSchemes = "Bearer")] ,否则就会返回404错误。

 

第三步:验证一下。

 

3.6、自定义用户模型,添加初始化数据

  修改用户模型之前,先来介绍一下IdentityServer框架默认添加的用户数据表功能,看下图。

 

第一步:在项目文件夹 Models 下创建自定义用户模型ApplicationUser.cs,继承自IdentityServer框架的IdentityUser。

 1 using Microsoft.AspNetCore.Identity;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace Demo19_IdentityServerJWT.Models
 8 {
 9     public class ApplicationUser:IdentityUser
10     {
11         public string Address { get; set; } //添加拓展的字段
12         //建立代码和模型的关系
13         public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; } //用户角色
14         public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; } //用户权限声明
15         public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; } //用户第三方登录信息
16         public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; } //用户登录的session
17     }
18 }
ApplicationUser.cs

第二步:把项目里使用的全部 IdentityUser类 替换成我们自定义的 ApplicationUser 类, Startup.cs 、 AppDbContext.cs 和 AuthenticateController.cs 。

第三步:更改项目文件夹 Database 下AppDbContext.cs,重写 protected override void OnModelCreating(ModelBuilder modelBuilder) 方法,加入初始化数据库代码。

 1 using Demo19_IdentityServerJWT.Models;
 2 using Microsoft.AspNetCore.Identity;
 3 using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
 4 using Microsoft.EntityFrameworkCore;
 5 using System;
 6 using System.Collections.Generic;
 7 using System.Linq;
 8 using System.Threading.Tasks;
 9 
10 namespace Demo19_IdentityServerJWT.Database
11 {
12     public class AppDbContext : IdentityDbContext<ApplicationUser>
13     {
14         public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
15         {
16         }
17         public DbSet<BookDto> BookDtos { get; set; }
18         protected override void OnModelCreating(ModelBuilder builder)
19         {
20             // 初始化用户与角色的种子数据
21             // 1. 更新用户ApplicationUser与角色IdentityUserRole的外键关系
22             builder.Entity<ApplicationUser>(b => {
23                 b.HasMany(x => x.UserRoles)
24                 .WithOne() //HasMany表示和一对多的关系
25                 .HasForeignKey(ur => ur.UserId) //使用IdentityUserRole的UserId作为外键
26                 .IsRequired(); //不能为空必填
27             });
28 
29             // 2. 添加管理员角色
30             var adminRoleId = "308660dc-ae51-480f-824d-7dca6714c3e2"; // guid 
31             builder.Entity<IdentityRole>().HasData(
32                 new IdentityRole
33                 {
34                     Id = adminRoleId,
35                     Name = "Admin",
36                     NormalizedName = "Admin".ToUpper()
37                 }
38             );
39 
40             // 3. 添加用户
41             var adminUserId = "90184155-dee0-40c9-bb1e-b5ed07afc04e";
42             ApplicationUser adminUser = new ApplicationUser
43             {
44                 Id = adminUserId,
45                 UserName = "BigBox777",
46                 NormalizedUserName = "BigBox777".ToUpper(),
47                 Email = "BigBox777@donesoft.cn",
48                 NormalizedEmail = "BigBox777@donesoft.cn".ToUpper(),
49                 TwoFactorEnabled = false,
50                 EmailConfirmed = true,
51                 PhoneNumber = "13888888888",
52                 PhoneNumberConfirmed = false
53             };
54             PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>();
55             adminUser.PasswordHash = ph.HashPassword(adminUser, "Abcdef123$");
56             builder.Entity<ApplicationUser>().HasData(adminUser);
57 
58             // 4. 给用户加入管理员权限
59             // 通过使用 linking table:IdentityUserRole
60             builder.Entity<IdentityUserRole<string>>().HasData(
61                 new IdentityUserRole<string>()
62                 {
63                     RoleId = adminRoleId,
64                     UserId = adminUserId
65                 });
66             base.OnModelCreating(builder);
67         }
68     }
69 }
AppDbContext.cs

第四步:打开Visual Studio菜单工具=》Nuget包管理器=》程序包管理器控制台 中输入命令 EntityFrameworkCore\Enable-Migrations 打开迁移。

第五步:以上就完成了用户模型的扩展功能,可以登录测试一下。

 

4、参考

  EF数据迁移完整步骤 https://www.cnblogs.com/bigbox777/p/13907142.html



这篇关于C#技术栈入门到精通系列19——鉴权授权IdentityServer JWT的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程