目录
前言
一、创建上下文类
1.自定义MyContext上下文类继承IdentityDbContext
2.在Program中添加AddDbContext服务
二、使用Migration数据迁移
1.在控制台中 依次使用add-migration 、updatebase 命令
2.如何修改表名
3.如何自定义字段
三、使用Identity实现登录、修改密码
1.在Program中 添加AddIdentityCore服务、AddRoleManager、AddUserManager配置
2.在控制器注入UserManager、RoleManager服务
四、使用JWT实现权限验证
1.在启动类Program.cs中配置Swagger可以输入身份验证方式
2.配置类信息、AddAuthentication服务
3.在登录的接口中返回token
4.在需要鉴权的接口加上 [Authorize]
总结
前言
identity
ASP.NET Core提供了标识(identity)框架,它采用RBAC(role-based access control,基于角色的访问控制)策略,内置了对用户、角色等表的管理及相关的接口,从而简化了系统的开发。
CodeFirst
先创建实体类,再通过实体类反向的创建数据库和表结构
什么是JWT?
JSON WEB Token,是一种基于JSON的、用于在网络上声明某种主张的令牌(token)
JWT组成
JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)
头信息指定了该JWT使用的签名算法,HS256 表示使用了 HMAC-SHA256 来生成签名。
消息体包含了JWT的意图
未签名的令牌由base64url编码的头信息和消息体拼接而成(使用"."分隔),签名则通过私有的key计算而成。
最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用"."分隔)就是JWT了
典型的JWT的格式:xxxxx.yyyyy.zzzzz
一、创建上下文类
安装Microsoft.EntityFrameworkCore
安装Microsoft.AspNetCore.Identity.EntityFrameworkCore
1.自定义MyContext上下文类继承IdentityDbContext
示例如下:
public class MyContext : IdentityDbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
2.在Program中添加AddDbContext服务
安装Microsoft.EntityFrameworkCore.SqlServer
示例如下:
builder.Services.AddDbContext<MyContext>(options =>
{
var connectionStr = builder.Configuration.GetConnectionString("SqlServer:Connection");
options.UseSqlServer(connectionStr);
});
在配置文件中appsettings.json配置连接字符串
"ConnectionStrings": {
"sqlserver": {
"Connection": "Server=服务器名称;User Id=账号;Password=密码;Database=数据库;MultipleActiveResultSets=true;Encrypt=True;TrustServerCertificate=True;"
}
}
二、使用Migration数据迁移
安装Microsoft.EntityFrameworkCore.Tools
1.在控制台中 依次使用add-migration 、updatebase 命令
如图所示
执行成功后 去数据库看数据库已经建立好了
效果如下:
2.如何修改表名
生成的表都默认是带有AspNet 觉得不喜欢,那怎么修改呢
使用 FluentAPI配置
示例如下:
public class UserConfig : IEntityTypeConfiguration<IdentityUser>
{
public void Configure(EntityTypeBuilder<IdentityUser> builder)
{
builder.ToTable("User");
}
}
public class RoleConfig : IEntityTypeConfiguration<IdentityRole>
{
public void Configure(EntityTypeBuilder<IdentityRole> builder)
{
builder.ToTable("Role");
}
}
public class UserRoleConfig : IEntityTypeConfiguration<IdentityUserRole<string>>
{
public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder)
{
builder.ToTable("UserRole");
}
}
在OnModelCreating方法中加入
// 反射中找项目下所有 继承IEntityTypeConfiguration的配置
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
再次执行add-migration 、updatebata 命令
再去数据库查看
已经成功修改我们想要的表名了
3.如何自定义字段
比如我想在用户表中添加年龄字段,创建新的用户类去继承IdentityUser类
示例如下:
public class User: IdentityUser
{
/// <summary>
/// 年龄
/// </summary>
public int? Age { get; set; }
/// <summary>
/// 备注
/// </summary>
public string ReMark { get; set; }
}
在UserConfig类中修改成User
public class UserConfig : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.Property(x => x.Id).HasColumnOrder(1);//字段排序
builder.Property(x => x.Age).IsRequired(false); //可以为空
builder.Property(x => x.ReMark).HasMaxLength(200).IsRequired(false); //指定长度 ,可以为空
builder.ToTable("User");
}
}
注意:上下文MyContext:IdentityDbContext需要修改成MyContext:IdentityDbContext<User>
这时候 再去执行migration命令,再去看数据库,已经加上了
效果如下:
三、使用Identity实现登录、修改密码
1.在Program中 添加AddIdentityCore服务、AddRoleManager、AddUserManager配置
示例如下:
builder.Services.AddIdentityCore<User>(options =>
{
//配置用户名
options.User = new UserOptions
{
RequireUniqueEmail = false, //要求Email唯一
//AllowedUserNameCharacters = "abcdefgABCDEFG123456789" //允许的用户名字符,默认是 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+
};
//配置密码
options.Password = new PasswordOptions
{
RequiredLength = 6, //要求密码最小长度,默认是 6 个字符
RequireDigit = true, //要求有数字
RequiredUniqueChars = 1, //要求至少要出现的字母数
RequireLowercase = false, //要求小写字母
RequireNonAlphanumeric = false, //要求特殊字符
RequireUppercase = false //要求大写字母
};
//锁定账户
options.Lockout = new LockoutOptions
{
AllowedForNewUsers = true, // 新用户锁定账户
DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1), //锁定时长,默认是 5 分钟
MaxFailedAccessAttempts = 3 //登录错误最大尝试次数,默认 5 次
};
//令牌配置
//打开此 设置 为 短验证码 不打开为 长验证码
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
var idBuilder = new IdentityBuilder(typeof(User), typeof(IdentityRole), builder.Services);
idBuilder.AddEntityFrameworkStores<MyContext>()
.AddDefaultTokenProviders()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddUserManager<UserManager<User>>();
2.在控制器注入UserManager、RoleManager服务
示例如下:
[ApiController]
[Route("[controller]/[action]")]
public class UserController : ControllerBase
{
private readonly UserManager<User> _userManager;
public UserController(UserManager<User> userManager)
{
_userManager = userManager;
}
/// <summary>
/// 创建用户
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> CreateUser(LoginRequest loginRequest)
{
User user = await _userManager.FindByNameAsync(loginRequest.UserName);
if (user == null)
{
user = new User
{
UserName = loginRequest.UserName
};
var result = await _userManager.CreateAsync(user, loginRequest.Password);
if (!result.Succeeded)
{
return BadRequest(result.Errors);
}
}
return Ok();
}
/// <summary>
/// 登录
/// </summary>
/// <param name="loginRequest"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> Login(LoginRequest loginRequest)
{
string userName = loginRequest.UserName;
string password = loginRequest.Password;
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return NotFound($"用户名{userName}不存在!");
}
var islocked = await _userManager.IsLockedOutAsync(user);
if (islocked)
{
return BadRequest("用户已锁定!");
}
var success = await _userManager.CheckPasswordAsync(user, password);
if (success)
{
return Ok();
}
else
{
var r = await _userManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
return BadRequest("访问失败信息写入错误!");
}
else
{
return BadRequest("失败!");
}
}
}
/// <summary>
/// 修改密码
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> ChangePassword(
ChangePasswordRequest req)
{
var user = await _userManager.FindByNameAsync(req.UserName);
if (user == null)
{
return NotFound($"用户名{req.UserName}不存在!");
}
var result = await _userManager.ChangePasswordAsync(user,req.oldPassword,req.newPassWord);
if (!result.Succeeded)
{
return BadRequest("修改失败!");
}
return Ok("Success");
}
#region 通过发送邮箱的方式重置密码
/// <summary>
/// 重置密码发送Token
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> SendResetPasswordToken(
SendResetPasswordTokenRequest req)
{
string email = req.Email;
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"邮箱不存在{email}");
}
string token = await _userManager.GeneratePasswordResetTokenAsync(user);
return Ok($"向邮箱{user.Email}发送Token={token}");
}
/// <summary>
/// 重置密码
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> ResetPasswordToken(
ResetPasswordRequest req)
{
string userName = req.UserName;
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return NotFound($"用户名{userName}不存在!");
}
var islocked = await _userManager.IsLockedOutAsync(user);
if (islocked)
{
return BadRequest("用户已锁定!");
}
var result = await _userManager.ResetPasswordAsync(user, req.token,req.newPassWord);
if (!result.Succeeded)
{
return BadRequest("修改失败!");
}
return Ok("Success");
}
#endregion
}
public record LoginRequest(string UserName, string Password);
public record ChangePasswordRequest(string UserName, string oldPassword,string newPassWord);
public record SendResetPasswordTokenRequest(string Email);
public record ResetPasswordRequest(string UserName, string token,string newPassWord);
四、使用JWT实现权限验证
安装Microsoft.AspNetCore.Authentication.JwtBearer
1.在启动类Program.cs中配置Swagger可以输入身份验证方式
示例如下:
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "请输入token,格式为 Bearer xxxxxxxx(注意中间必须有空格)",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
//添加安全要求
options.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme{
Reference =new OpenApiReference{
Type = ReferenceType.SecurityScheme,
Id ="Bearer"
}
},new string[]{ }
}
});
});
2.配置类信息、AddAuthentication服务
示例如下:
public class JWTOptions
{
/// <summary>
/// 颁发者
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 接收者
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 密钥
/// </summary>
public string SigningKey { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public int ExpireSeconds { get; set; }
}
在配置文件appsettings.json中加入以下信息
"JWT": {
"Issuer": "我是小小鱼",
"Audience": "我是小小鱼",
"SigningKey": "fasdfad&9045dafz222#fadpio@0232",
"ExpireSeconds": "3600"
}
在添加AddAuthentication服务
builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
var secKey = new SymmetricSecurityKey(keyBytes);
x.TokenValidationParameters = new()
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidIssuer = jwtOpt.Issuer,
ValidAudience=jwtOpt.Audience,
IssuerSigningKey = secKey,
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(4)
};
});
创建一个Jwt辅助类
public class JwtHelper
{
public static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
{
DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
var secKey = new SymmetricSecurityKey(keyBytes);
var credentials = new SigningCredentials(secKey,
SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new JwtSecurityToken(
options.Issuer,
options.Audience,
expires: expires,
signingCredentials: credentials, claims: claims);
return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
}
}
3.在登录的接口中返回token
示例如下:
/// <summary>
/// 登录
/// </summary>
/// <param name="loginRequest"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> Login(LoginRequest loginRequest,[FromServices] IOptions<JWTOptions> jwtOptions)
{
string userName = loginRequest.UserName;
string password = loginRequest.Password;
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return NotFound($"用户名{userName}不存在!");
}
var islocked = await _userManager.IsLockedOutAsync(user);
if (islocked)
{
return BadRequest("用户已锁定!");
}
var success = await _userManager.CheckPasswordAsync(user, password);
if (success)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
var roles = await _userManager.GetRolesAsync(user);
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
string Token = JwtHelper.BuildToken(claims, jwtOptions.Value);
return Ok(Token);
}
else
{
var r = await _userManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
return BadRequest("访问失败信息写入错误!");
}
else
{
return BadRequest("失败!");
}
}
}
效果如下
4.在需要鉴权的接口加上 [Authorize]
示例如下:
/// <summary>
/// 获取用户信息
/// </summary>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<IActionResult> GetUser() {
var claimsPrincipal = this.HttpContext.User;
var name = claimsPrincipal.Claims.FirstOrDefault(r => r.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.Value;
var user = await _userManager.FindByNameAsync(name);
if (user == null)
{
return BadRequest("token有误");
}
return Ok($"获取用户名:{user.UserName},邮箱:{user.Email}");
}
运行效果
总结
以上简单用Identity框架在通过migration命令建库建表,再使用 FluentAPI配置表名、字段,用dentity框架封装的UserManager实现登录、修改密码,以及通过token实现鉴权