您现在的位置是:网站首页> .NET Core
Asp.Net Core Identity 完成注册登录
- .NET Core
- 2022-02-05
- 721人已阅读
Identity是Asp.Net Core全新的一个用户管理系统,它是一个完善的全面的庞大的框架,提供的功能有:
创建、查询、更改、删除账户信息
验证和授权
密码重置
双重身份认证
支持扩展登录,如微软、Facebook、google、QQ、微信等
提供了一个丰富的API,并且这些API还可以进行大量的扩展
接下来我们先来看下它的简单使用。首先在我们的DbContext中需要继承自IdentityDbContext。
复制代码
public class AppDbContext:IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
{
}
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Seed();
}
}
复制代码
然后在Startup中注入其依赖,IdentityUser和IdentityRole是Identity框架自带的两个类,将其绑定到我们定义的AppDbContext中。
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
最后需要添加中间件UseAuthentication。
复制代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//如果环境是Development,调用 Developer Exception Page
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePages();
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
复制代码
接下来我们就可以使用数据库迁移 Add-Migration 来添加迁移,然后update-database我们的数据库。
可以在数据库中看到其生成的表。
在完成数据迁移之后,我们再来看下Identity中如何完成用户的注册和登录。
我们定义一个ViewModel,然后定义一个AccountController来完成我们的注册和登录功能。Asp.Net Core Identity为我们提供了UserManger来对用户进行增删改等操作,提供了SignInManager的SignInAsync来登录,SignOutAsync来退出,IsSignedIn来判断用户是否已登录等。
复制代码
public class RegisterViewModel
{
[Required]
[Display(Name = "邮箱地址")]
[EmailAddress]
public string Email { get; set; }
[Required]
[Display(Name = "密码")]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "确认密码")]
[Compare("Password",
ErrorMessage = "密码与确认密码不一致,请重新输入.")]
public string ConfirmPassword { get; set; }
}
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "记住我")]
public bool RememberMe { get; set; }
}
复制代码
复制代码
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using StudentManagement.ViewModels;
using System.Threading.Tasks;
namespace StudentManagement.Controllers
{
public class AccountController:Controller
{
private UserManager<IdentityUser> userManager;
private SignInManager<IdentityUser> signInManager;
public AccountController(UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
this.userManager = userManager;
this.signInManager = signInManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
//将数据从RegisterViewModel复制到IdentityUser
var user = new IdentityUser
{
UserName = model.Email,
Email = model.Email
};
//将用户数据存储在AspNetUsers数据库表中
var result = await userManager.CreateAsync(user, model.Password);
//如果成功创建用户,则使用登录服务登录用户信息
//并重定向到home econtroller的索引操作
if (result.Succeeded)
{
await signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("index", "home");
}
//如果有任何错误,将它们添加到ModelState对象中
//将由验证摘要标记助手显示到视图中
foreach (var error in result.Errors)
{
if (error.Code== "PasswordRequiresUpper")
{
error.Description = "密码必须至少有一个大写字母('A'-'Z')。";
}
//PasswordRequiresUpper
//Passwords must have at least one uppercase ('A'-'Z').
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
return RedirectToAction("index", "home");
}
[HttpGet]
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var result = await signInManager.PasswordSignInAsync(
model.Email, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("index", "home");
}
}
ModelState.AddModelError(string.Empty, "登录失败,请重试");
}
return View(model);
}
}
}
复制代码
复制代码
@model RegisterViewModel
@{
ViewBag.Title = "用户注册";
}
<h1>用户注册</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
</div>
</div>
复制代码
复制代码
@model LoginViewModel
@{
ViewBag.Title = "用户登录";
}
<h1>用户登录</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
复制代码
在实际工作中,我们需要配置密码的复杂度来增强用户信息的安全性。而Asp.Net Core Identity也默认也提供了一套机制PasswordOptions,可以查看其源码。
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/PasswordOptions.cs
但是有时候我们需要自定义我们的密码校验模式,这时候可以在Startup中注入
复制代码
services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 3;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
});
复制代码
同时,我们希望我们在注册的时候提示错误信息时使用中文显示,可以定义一个继承IdentityErrorDescriber的类。
复制代码
using Microsoft.AspNetCore.Identity;
namespace StudentManagement.Middleware
{
public class CustomIdentityErrorDescriber : IdentityErrorDescriber
{
public override IdentityError DefaultError()
{
return new IdentityError { Code = nameof(DefaultError), Description = $"发生了未知的故障。" };
}
public override IdentityError ConcurrencyFailure()
{
return new IdentityError { Code = nameof(ConcurrencyFailure), Description = "乐观并发失败,对象已被修改。" };
}
public override IdentityError PasswordMismatch()
{
return new IdentityError { Code = nameof(PasswordMismatch), Description = "密码错误" };
}
public override IdentityError InvalidToken()
{
return new IdentityError { Code = nameof(InvalidToken), Description = "无效的令牌." };
}
public override IdentityError LoginAlreadyAssociated()
{
return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "具有此登录的用户已经存在." };
}
public override IdentityError InvalidUserName(string userName)
{
return new IdentityError { Code = nameof(InvalidUserName), Description = $"用户名'{userName}'无效,只能包含字母或数字." };
}
public override IdentityError InvalidEmail(string email)
{
return new IdentityError { Code = nameof(InvalidEmail), Description = $"Email '{email}' is invalid." };
}
public override IdentityError DuplicateUserName(string userName)
{
return new IdentityError { Code = nameof(DuplicateUserName), Description = $"User Name '{userName}' is already taken." };
}
public override IdentityError DuplicateEmail(string email)
{
return new IdentityError { Code = nameof(DuplicateEmail), Description = $"Email '{email}' is already taken." };
}
public override IdentityError InvalidRoleName(string role)
{
return new IdentityError { Code = nameof(InvalidRoleName), Description = $"Role name '{role}' is invalid." };
}
public override IdentityError DuplicateRoleName(string role)
{
return new IdentityError { Code = nameof(DuplicateRoleName), Description = $"Role name '{role}' is already taken." };
}
public override IdentityError UserAlreadyHasPassword()
{
return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "User already has a password set." };
}
public override IdentityError UserLockoutNotEnabled()
{
return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "Lockout is not enabled for this user." };
}
public override IdentityError UserAlreadyInRole(string role)
{
return new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"User already in role '{role}'." };
}
public override IdentityError UserNotInRole(string role)
{
return new IdentityError { Code = nameof(UserNotInRole), Description = $"User is not in role '{role}'." };
}
public override IdentityError PasswordTooShort(int length)
{
return new IdentityError { Code = nameof(PasswordTooShort), Description = $"密码必须至少是{length}字符." };
}
public override IdentityError PasswordRequiresNonAlphanumeric()
{
return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "密码必须至少有一个非字母数字字符."
};
}
public override IdentityError PasswordRequiresDigit()
{
return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = $"密码必须至少有一个数字('0'-'9')." };
}
public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
{
return new IdentityError { Code = nameof(PasswordRequiresUniqueChars), Description = $"密码必须使用至少不同的{uniqueChars}字符。" };
}
public override IdentityError PasswordRequiresLower()
{
return new IdentityError { Code = nameof(PasswordRequiresLower), Description = "密码必须至少有一个小写字母('a'-'z')." };
}
public override IdentityError PasswordRequiresUpper()
{
return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "密码必须至少有一个大写字母('A'-'Z')." };
}
}
}
复制代码
最后需要在注入Identity的时候添加上这个类
services.AddIdentity<IdentityUser, IdentityRole>()
.AddErrorDescriber<CustomIdentityErrorDescriber>()
.AddEntityFrameworkStores<AppDbContext>();
完成登录后,我们需要对访问资源进行授权,需要在controller或者action上使用Authorize属性来标记,也可以使用AllowAnonymous来允许匿名访问,在项目中使用授权需要引入中间件UseAuthentication
app.UseAuthentication();
但是如果项目中有很多controller需要添加Authorize属性,我们可以在startup中添加全局的授权,代码如下。
复制代码
services.AddMvc(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
复制代码
一般在用户登录成功后需要重定向到原始的 URL,这个通过请求参数中带returnUrl来实现,但是如果没有判断是否本地的Url时则会引发开放式重定向漏洞。
解决开放式重定向漏洞的方式也很简单,就是在判断的时候添加Url.IsLocalUrl或者直接return LocalRedirect。
if (Url.IsLocalUrl(returnUrl))
{
}
return LocalRedirect(returnUrl);