旅游网项目的身份验证功能的实现

Session:每个用户通过服务端的认证后,服务端都要做一次记录,便于下次请求的鉴别,这样就会增加服务器的内存负担;
Session的扩展性: 所以每次请求,都要确保我们曾经认证的记录存储在我们当前要访问的服务器上,负载均衡就受到了很大的限制;

什么是JWT?-> JSON WEB TOKEN

JWT包含了三段信息
header 头部 -> 类型,加密算法

{
  'typ': 'JWT',   
  'alg': 'HS256'
}

payload 载荷

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方(Token的持有者)
exp: jwt的过期时间,且大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

公共的声明:
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

signature 签证

header (base64后的)+
payload (base64后的)+
secret

下面我们正式开始创建数据库,配置等

1.我们在模型中创建一个新的类:ApplicationUser.cs 需要继承自:IdentityUser

这是在IdentityUser提供的属性不够满足我们项目的使用的情况下,否则我们直接使用IdentityUser即可
下面的Virtual为添加的一些关联其他的表的属性

    public class ApplicationUser:IdentityUser
    {
        public string Address { get; set; }

        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; }


    }

2.我们需要在StartUp文件中添加服务
这里一共添加了2个服务,第一个为创建相对应的数据库而添加的服务
第二个为Jwt的验证配置服务,我们可以选择是否验证发布者,持有者,以及过期时间
这其中所用的参数,我们是保存在Appsetting中的,使用—IConfiguration服务获取

            services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<AppDbContext>();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(option =>
                {
                    var secretBytes = Encoding.UTF8.GetBytes(_Configuration["Authentication:SecretKey"]);
                    option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    {
                        ValidateIssuer = true,//验证发布者
                        ValidIssuer = _Configuration["Authentication:Issuer"],

                        ValidateAudience = true,//验证Token持有者
                        ValidAudience = _Configuration["Authentication:Audience"],

                        ValidateLifetime = true,//验证是否过期
                        IssuerSigningKey=new SymmetricSecurityKey(secretBytes)//传入私钥
                    };
                });

3.在管道中间添加验证授权中间件
这对顺序是有要求的

            //我在哪
            app.UseRouting();
            //我是谁
            app.UseAuthentication();
            //我可以干什么
            app.UseAuthorization();

4.在这里我们新建一个控制器(AuthenticationController)在这个控制器中进行注册登录验证等操作
这里面包含了2个Action :Login 、register

注入两个主要的服务:UserManager、SignInManager,分别为用户管理,和登录管理

首先在registerAction中使用UserManager服务,创建一个新的用户,这个时候数据库创建了一个用户
然后我们使用该用户的用户名和密码的Json格式作为HttpBody,传入LoginAction方法中
New一个新的Claim里面包含了Sub,该Token的发布者
随后返回一个Token,
因为Jwt没有通过中间件进行验证所以对于要访问的方法前的标签应该为:
[Authorize(AuthenticationSchemes ="Bearer")]
[Authorize(Roles ="Admin")]

之后我们每次访问需要验证授权的控制器方法的时候
只需要在Http请求的Headers添加Authentication=bearer(这里有一个空格)+(Token,当然前提是Token有效)就可以访问相应的控制器方法了

 [Route("Auth")]
    public class AuthenticateController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly SignInManager<ApplicationUser> _signInManager;

        public AuthenticateController(
            IConfiguration configuration,
            UserManager<ApplicationUser> userManager,
            SignInManager<ApplicationUser> signInManager)
        {
            _configuration = configuration;
            _userManager = userManager;
            _signInManager = signInManager;
        }
        [AllowAnonymous]
        [Route("Login")]
        [HttpPost]
        public async Task<IActionResult> Logi4n([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);
            var signingAlgorithm = SecurityAlgorithms.HmacSha256;
            //2.创建jwt
            var claims = new List<Claim>
            {
               // new Claim(ClaimTypes.Role,"Admin"),//添加角色
                new Claim(JwtRegisteredClaimNames.Sub,user.Id)
        };
            var roleNames = await _userManager.GetRolesAsync(user);//从数据库中拿到User的角色
            foreach (var roleName in roleNames)//将该对象的所有Claim遍历存储到claims,在后面添加到token中一同返回
            {
                var roleClaim = new Claim(ClaimTypes.Role,roleName);
                claims.Add(roleClaim);
            }
            var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
            var signingKey = new SymmetricSecurityKey(secretByte);//非对称算法进行加密
            var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);
            //第三四个参数:表示多次登录是否锁定账户
            var token = new JwtSecurityToken(
                issuer:_configuration["Authentication:Issuer"],//谁发布的
                audience: _configuration["Authentication:Audience"],//发布给谁
                claims,
                notBefore:DateTime.UtcNow,//发布时间
                expires:DateTime.UtcNow.AddDays(1),//有效时间
                signingCredentials
                );

            var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);//以string的格式输出token
            //3.return 200 ok+jwt
            return Ok(tokenStr);
        }

        [HttpPost("register")]
        [AllowAnonymous]
        public async Task<IActionResult> Register([FromBody] RegisterUserDto registerUserDto)
        {
            if (_userManager.FindByEmailAsync(registerUserDto.Email) != null)
            {
                return BadRequest("用户名已经存在");
            }
            //1.使用用户名创建用户对象
            var user = new ApplicationUser()
            {
                UserName = registerUserDto.Email,
                Email = registerUserDto.Email

            };
            //2.hash 保存密码
            var result = await _userManager.CreateAsync(user, registerUserDto.Password);
            if (!result.Succeeded)
            {
                return BadRequest("用户创建失败");
            }
            return Ok();
        }
    }

我们还需再AppDbcontext中添加一些子数据(包括管理员,还有之前我们重写了IdentityUser的ApplicationUser类型中list table 的一些关系)

 public class AppDbContext:IdentityDbContext<ApplicationUser> 
    {
        public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
        {

        }
        //指明哪些模型需要映射到数据库中
        public DbSet<TouristRoute> TouristRoutes { get; set; }//数据模型映射(每一张table都需要一个DbSet进行映射)
        public DbSet<TouristRoutePicture> TouristRoutePictures { get; set; }

        //添加种子数据
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //从Json读取种子数据
          // var JsonData =  File.ReadAllText(@"C:UsersFlower-Lisource
eposXiechengTravelXiechengTravelinDebug
et5.0DatabaseTouristRouteData.json");
           var JsonData =  File.ReadAllText(Path.GetDirectoryName(Directory.GetCurrentDirectory()) + @"XiechengTravelDatabaseTouristRouteData.json");
            IList<TouristRoute> touristRoutes = JsonConvert.DeserializeObject<IList<TouristRoute>>(JsonData);//反序列化,将string=>object

            var jsonPicData =  File.ReadAllText(Path.GetDirectoryName(Directory.GetCurrentDirectory()) + @"XiechengTravelDatabaseTouristRoutePictureData.json");
            IList<TouristRoutePicture> touristRoutePictures = JsonConvert.DeserializeObject<IList<TouristRoutePicture>>(jsonPicData);


            // 初始化用户与角色的种子数据
            // 1. 更新用户与角色的外键关系
            modelBuilder.Entity<ApplicationUser>(b => {
                b.HasMany(x => x.UserRoles)//有多个UserRoles
                .WithOne()//设置为一对多的关系
                .HasForeignKey(ur => ur.UserId)//UserId为外键
                .IsRequired();//不能为空
            });

            // 2. 添加角色
            var adminRoleId = "308660dc-ae51-480f-824d-7dca6714c3e2"; // guid 
            modelBuilder.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@fakexiecheng.com",
                NormalizedUserName = "admin@fakexiecheng.com".ToUpper(),
                Email = "admin@fakexiecheng.com",
                NormalizedEmail = "admin@fakexiecheng.com".ToUpper(),
                TwoFactorEnabled = false,
                EmailConfirmed = true,
                PhoneNumber = "123456789",
                PhoneNumberConfirmed = false
            };

            //加密密码
            PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>();
            adminUser.PasswordHash = ph.HashPassword(adminUser, "Fake123$");
            modelBuilder.Entity<ApplicationUser>().HasData(adminUser);

            // 4. 给用户加入管理员权限
            // 通过使用 linking table:IdentityUserRole
            modelBuilder.Entity<IdentityUserRole<string>>()
                .HasData(new IdentityUserRole<string>()
                {
                    RoleId = adminRoleId,
                    UserId = adminUserId
                });

            modelBuilder.Entity<TouristRoute>().HasData(touristRoutes);
            modelBuilder.Entity<TouristRoutePicture>().HasData(touristRoutePictures);
            base.OnModelCreating(modelBuilder);

        }
    }
原文地址:https://www.cnblogs.com/liflower/p/14724742.html