1. 程式人生 > 其它 >C#技術棧入門到精通系列19——鑑權授權IdentityServer JWT

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 = "[email protected]",
48                 NormalizedEmail = "[email protected]".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