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

 

posted @ 2021-04-14 19:05  点终将连成线  阅读(1074)  评论(0编辑  收藏  举报