JWT、用户授权与认证
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→用户授权→添加数据库表→注册用户→真实数据登录认证→测试授权操作。