旅游网项目的身份验证功能的实现
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:\Users\Flower-Li\source\repos\XiechengTravel\XiechengTravel\bin\Debug\net5.0\Database\TouristRouteData.json");
var JsonData = File.ReadAllText(Path.GetDirectoryName(Directory.GetCurrentDirectory()) + @"\XiechengTravel\Database\TouristRouteData.json");
IList<TouristRoute> touristRoutes = JsonConvert.DeserializeObject<IList<TouristRoute>>(JsonData);//反序列化,将string=>object
var jsonPicData = File.ReadAllText(Path.GetDirectoryName(Directory.GetCurrentDirectory()) + @"\XiechengTravel\Database\TouristRoutePictureData.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);
}
}