JWT:JSON Web Token ,作用:是用戶授權(Authorization),指的是用戶登錄以后是否有權限訪問特定的資源。錯誤狀態碼:403 forbidden
用戶的身份認證(Authentication):用戶認證指的是使用用戶名,密碼來驗證當前用戶的身份→用戶登錄。錯誤狀態碼:401 Unauthorized
JWT改變了傳統的Session與cookie的有狀態登錄,替換cookie,JWT信息只需要保存在客戶端,無狀態登錄,只解決授權的問題,跟登錄沒有關系;
打開jwt.io 傳入已經復制好的token進行解析:
JWT有三個部分:
header:頭部,具體描述當前的JWT的編碼算法,這個算法用於signature中數字簽名的驗證;
payload:保存具體的用戶信息;比如:id、姓名等等。iat表示JWT的創建時間,esp數據類型是毫秒,指的是JWT的有效時間;
signature:激光防偽標志。服務器通過這個數字簽名來判斷你所發的token是否有效(數字簽名使用的是非對稱加密算法(hs256),只能使用服務器的私鑰才解密);
JWT優點:無狀態,簡單,方便,完美支持分布式部署;非對稱加密,Token安全性高;
JWT缺點:無狀態,toeken一經發布則無法取消; 明文傳遞,token安全性低(使用https可以解決);
啟用JWT無狀態登錄系統:
一、首先安裝JWT的框架:
二、創建用戶登錄的token
添加認證的Controller並在Controller中添加登錄的API
登錄的參數:
public class LoginDto { [Required] public string Email { get; set; } [Required] public string Password { get; set; } }
[ApiController] [Route("auth")] public class AuthenticationController : ControllerBase { private readonly IConfiguration _configuration; //注入讀取配置文件的服務依賴 private readonly UserManager<ApplicationUser> _userManager; //添加Has密碼工具的服務依賴 private readonly SignInManager<ApplicationUser> _signInManager;//處理用戶的登錄驗證服務依賴 public AuthenticationController( IConfiguration configuration, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager ) { _configuration = configuration; _userManager = userManager; _signInManager = signInManager; } [AllowAnonymous] [HttpPost("login")] public async Task<IActionResult> login([FromBody] LoginDto loginDto) { // 1 驗證用戶名密碼 var loginResult = await _signInManager.PasswordSignInAsync( loginDto.Email, loginDto.Password, false, //賬號登錄是否持久性 false //報錯是否把賬號鎖起來 ); if (!loginResult.Succeeded) { return BadRequest(); } var user = await _userManager.FindByNameAsync(loginDto.Email); //驗證成功取用戶數據 // 2 創建jwt // header var signingAlgorithm = SecurityAlgorithms.HmacSha256;//定義了數字簽名算法 // payload var claims = new List<Claim> //JWT中自定義payload的數據 { // sub 用戶ID new Claim(JwtRegisteredClaimNames.Sub, user.Id),//把驗證好的用戶數據放到claims中 //new Claim(ClaimTypes.Role, "Admin")//用戶角色 }; var roleNames = await _userManager.GetRolesAsync(user);//獲得用戶所有的角色 foreach (var roleName in roleNames) { var roleClaim = new Claim(ClaimTypes.Role, roleName); //把用戶列表轉化為claim claims.Add(roleClaim);//用來生成JWT token } // signiture 數字簽名 var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]); //將私鑰變成UTF8格式,私鑰放在項目的配置文件中 var signingKey = new SymmetricSecurityKey(secretByte); //使用非對稱加密算法將私鑰進行加密 var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm); //使用hs256驗證加密后的私鑰→跟header部分驗證 //使用上面的所有數據創建JWT的token var token = new JwtSecurityToken( issuer: _configuration["Authentication:Issuer"], //發布者 audience: _configuration["Authentication:Audience"], //接收者(項目的前端) claims, //payload數據 notBefore: DateTime.UtcNow, //發布時間 expires: DateTime.UtcNow.AddDays(1), //有效時間 signingCredentials //數字簽名 ); var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);//把token以字符串的形式輸出 // 3 return 200 ok + jwt return Ok(tokenStr); //輸出token }
配置文件中的數據:
三、啟動授權API(使用已經創建的token來訪問受保護的資源)
注入JWT的身份驗證服務,啟動用戶授權的框架:
// 給項目注入JWT身份認證服務,同時啟動用戶授權的框架 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme/*JWT認證類型*/) .AddJwtBearer(options => //配置JWT認證 { var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:SecretKey"]); options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidIssuer = Configuration["Authentication:Issuer"], //驗證token的發布者(后端) ValidateAudience = true, //驗證token的持有者(前端) ValidAudience = Configuration["Authentication:Audience"], ValidateLifetime = true, //驗證token是否過期 IssuerSigningKey = new SymmetricSecurityKey(secretByte) //加密私鑰 }; });
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. //服務框架的啟動,注意啟動的順序 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // 你在哪? app.UseRouting(); // 你是誰? app.UseAuthentication(); // 你可以干什么?有什么權限? app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
給API加上用戶角色授權:(凡是進行權限認證的Action都加上以下的特性)
四、使用身份認證框架
安裝框架:
注冊框架的服務依賴:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
添加新的用戶模型:
public class ApplicationUser : IdentityUser { public string Address { get; set; } // ShoppingCart // Order public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; } public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; } public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; } public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; } }
初始化數據庫,添加用戶表,角色表等:
public class AppDbContext : IdentityDbContext<ApplicationUser> { /// <summary> /// 注入DbContext實例,這個實例可以通過構建函數的實例傳遞進來 /// </summary> /// <param name="options"></param> public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } // 初始化用戶與角色的種子數據 // 1. 更新用戶與角色的外鍵關系 modelBuider.Entity<ApplicationUser>(b => { b.HasMany(x => x.UserRoles) .WithOne() .HasForeignKey(ur => ur.UserId) .IsRequired(); });
// 2. 添加角色 var adminRoleId = "308660dc-ae51-480f-824d-7dca6714c3e2"; // guid modelBuider.Entity<IdentityRole>().HasData( new IdentityRole { Id = adminRoleId, Name = "Admin", NormalizedName = "Admin".ToUpper() } ); // 3. 添加用戶 var adminUserId = "90184155-dee0-40c9-bb1e-b5ed07afc04e"; ApplicationUser adminUser = new ApplicationUser { Id = adminUserId, UserName = "admin*********.com", NormalizedUserName = "admin*********.com".ToUpper(), Email = "admin*******.com", NormalizedEmail = "admin*******.com".ToUpper(), TwoFactorEnabled = false, EmailConfirmed = true, PhoneNumber = "123456789", PhoneNumberConfirmed = false }; PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>(); //密碼的Hash工具 adminUser.PasswordHash = ph.HashPassword(adminUser, "******");//hash密碼 modelBuider.Entity<ApplicationUser>().HasData(adminUser); // 4. 給用戶加入管理員權限 // 通過使用 linking table:IdentityUserRole modelBuider.Entity<IdentityUserRole<string>>() .HasData(new IdentityUserRole<string>() { RoleId = adminRoleId, UserId = adminUserId }); base.OnModelCreating(modelBuider); } }
五、使用命令行更新數據庫
六、添加用戶注冊API:
注冊的參數:
public class RegisterDto { [Required] public string Email { get; set; } [Required] public string Password { get; set; } [Required] [Compare(nameof(Password), ErrorMessage = "密碼輸入不一致")] public string ConfirmPassword { get; set; } }
//private readonly UserManager<ApplicationUser> _userManager; //添加Has密碼工具的服務依賴
[AllowAnonymous] [HttpPost("register")] public async Task<IActionResult> Register([FromBody] RegisterDto registerDto) { // 1 使用用戶名創建用戶對象 var user = new ApplicationUser() { UserName = registerDto.Email, Email = registerDto.Email }; // 2 hash密碼,保存用戶 var result = await _userManager.CreateAsync(user, registerDto.Password); if (!result.Succeeded) { return BadRequest(); } // 3 return return Ok(); }
以上整個用戶注冊、登錄、認證、授權就完成了,可能順序有點亂,因為我先是利用假數據登錄→創建JWT Token→用戶授權→添加數據庫表→注冊用戶→真實數據登錄認證→測試授權操作。