回顾
朋友们,距离上次从0开始.NET CORE认证-1发布已经过去一周了,上次第一篇文章,其实并没有涉及到Net Core Identity,就是简单的搭了一个项目,让大家对Identity中各种术语有个理解,明白他们出现的位置,已经他们出现能够达到某种功能。以及出现的位置顺序不同,则会出现什么不同的情况。
回顾一下上次写的主要的知识点
- Authentication和Authorization 是什么,怎么解释他们
- Claim和ClaimType又是什么,能举例子说明吗?
- ClaimsIdentity和ClaimsPrincipal的含义是什么,他们的从属关系是什么样的?
- app.UseAuthorization()和app.UseAuthentication()的意义是什么,能不能调换?
如果你对上面的问题都能回答,我相信你已经看懂了第一篇我讲了什么。
介绍
本章,我将会正式引入.Net Core Identity,然后还会引入EF Core,将.Net Core Identity的用户数据通过EF Core持久化到数据库中,用大白话就是把用户数据保存到数据库,以下思维导图的部分就是我们要做的部分
本文含有大量GIF图,请耐心等待加载
创建项目
我们继续使用第一篇文章中的解决方案,然后右键——添加——新建项目,选择ASP.NET CORE Web 应用程序,项目名称我们取为:AspNetCoreIdentityExample
同样,我们需要MVC框架来帮助我们搭建前端页面,所以修改一下StartUp.cs的内容,可以直接从上个项目BasiclyIdentity中复制一个基本代码,使其修改成这样。
1 public class Startup 2 { 3 public void ConfigureServices(IServiceCollection services) 4 { 5 services.AddControllersWithViews(); 6 } 7 8 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 9 { 10 if (env.IsDevelopment()) 11 { 12 app.UseDeveloperExceptionPage(); 13 } 14 15 app.UseRouting(); 16 17 app.UseEndpoints(endpoints => 18 { 19 endpoints.MapDefaultControllerRoute(); 20 }); 21 } 22 }
具体操作看下面的图
然后我们创建一个控制器名为HomeController,里面定义两个Action,一个为Index,另外一个为Secert。也创建两个视图。我们可以直接复制BasiclyIdentity的视图和控制器,并且运行。查看能否正常运行。
具体操作请看图(
tips:一个解决方案内有多个可启动项目,注意调试的时候选择正确的项目):
调试,发现可以成功运行了。那我们可以开展下一步了
配置框架
我们将会使用.Net Core Identity和EF Core帮助我们管理用户,他并不会默认自带在我们创建的项目中
所以我们需要引入五个Nuget包:
- Microsoft.AspNetCore.Identity
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
这五个包负责不同的功能,
- Microsoft.AspNetCore.Identity //包含AspNetCore.Identity的框架,我们可以使用里面认证授权等功能,最基础的功能
- Microsoft.AspNetCore.Identity.EntityFrameworkCore //对AspNetCore.Identity的扩展,让其可以和EF Core配合使用
- Microsoft.EntityFrameworkCore //EF Core 这个不用我多说了吧
- Microsoft.EntityFrameworkCore.SqlServer //EF Core用来连接sql server的包,如果使用mysql,最后一个换名字就行了
- Microsoft.EntityFrameworkCore.Tools //在VS 命令行里面迁移数据需要用的命令 add-migration update-database
然后安装好这五个包之后,创建一个数据库(这个数据库可以是专门存放用户信息的数据库,也可以是跟业务耦合在一起的数据库)因为这个只是关于用户管理的示例项目,所以没有业务。所以我会创建一个AppUserDb的数据库,在项目中的appsetting.json中会配置好连接字符串
链接字符串的格式
Server=.;Database=TBS;User ID=AppUserDb;Password=Dd112233;Trusted_Connection=False;MultipleActiveResultSets=true
具体操作看图-安装NuGet包
创建数据库和配置连接字符串
配置EF Core
我们先开始在项目下面创建一个Data文件夹,然后创建数据库上下文AppDbContext.cs,让其继承DbContext,然后在StartUp.cs中注入这个上下文,随后执行迁移命令,通过EF Core在数据库内生成相应的表。
创建上下文,并注入。主要是以下代码
1 using Microsoft.EntityFrameworkCore; 2 3 namespace AspNetCoreIdentityExample.Data 4 { 5 public class AppDbContext:DbContext 6 { 7 public AppDbContext(DbContextOptions<AppDbContext> options) 8 :base(options) 9 { 10 11 } 12 } 13 }
1 services.AddDbContext<AppDbContext>(config => 2 { 3 config.UseSqlServer(configuration.GetSection("ConnectionString").Value, opt => 4 { 5 //opt.CommandTimeout(6000); 6 }); 7 });
具体操作看图:
配置.Net Core Identity
我们在StartUp.cs文件中引用.Net Core Identity,其中IdentityUser和IdentityRole是框架内置的用户类和角色类,如果我们需要做扩展,只需要继承此类即可
主要是以下代码
1 services.AddIdentity<IdentityUser, IdentityRole>() 2 .AddDefaultTokenProviders();
你认为到此结束了吗?其实还没有,在第一篇文章中,我们使用了Cookie作为认证授权的条件,但是我们这里好像没有指定什么东西作为登录授权的条件。所以我们也要为.Net Core Identity开启Cookies认证(.Net Core Identity不仅仅支持Cookie,此话后文再说)
1 services.ConfigureApplicationCookie(config => 2 { 3 config.Cookie.Name = ".NetCoreIdentity.Cookies"; 4 config.LoginPath = "/Home/Login"; //让没有Cookie的用户访问被保护的接口时候跳转到这个Api 5 });
具体操作看下图:
同理,我们既然指定了当用户没有授权的时候,要跳转到/Home/Login,所以我们要新增两个页面一个是登录页,一个是注册页
在HomeController里面增加两个Action,使其变成下面的代码,然后增加两个页面一个是Login.cshtml一个是Register.cshtml,代码如下
1 using Microsoft.AspNetCore.Authentication; 2 using Microsoft.AspNetCore.Authorization; 3 using Microsoft.AspNetCore.Mvc; 4 using System.Collections.Generic; 5 using System.Security.Claims; 6 7 namespace AspNetCoreIdentityExample.Controllers 8 { 9 public class HomeController : Controller 10 { 11 public IActionResult Index() 12 { 13 return View("Index"); 14 } 15 16 [Authorize] 17 public IActionResult Secert() 18 { 19 return View("Secert"); 20 } 21 22 [HttpGet] 23 public IActionResult Login() 24 { 25 return View("Login"); 26 } 27 28 [HttpGet] 29 public IActionResult Register() 30 { 31 return View("Register"); 32 } 33 } 34 }
1 <h1>登录页</h1> 2 <form method="post" formaction="/Home/Login"> 3 <input name="username" type="text" value="" /> 4 <input name="password" type="password" value="" /> 5 <button>登录</button> 6 </form> 7 <a href="/Home/Register">没有账号去注册</a>
1 <h1>注册页</h1> 2 <form method="post" formaction="/Home/Register"> 3 <input name="username" type="text" value="" /> 4 <input name="password" type="password" value="" /> 5 <button>注册</button> 6 </form> 7 <a href="/Home/Login">已有账号去登陆</a>
操作请看图
好了,现在我们想一想我们的准备工作还差什么,我们已经有了登录、注册页面,然后认证方式也选了Cookie,我们也配置了数据库。
所以,我们目前还少了将登录、注册的业务逻辑、将用户数据持久化到数据库的代码。还有一个最重要的就是调用UseAuthorization()和UseAuthentication()方法,如果你不用这个方法,当访问带有[Authorize]标签的控制器的时候,就会出错,所以我们在StartUp.cs内配置一下
将Identity和EF Core结合起来
还记得我们安装了一个Microsoft.AspNetCore.Identity.EntityFrameworkCore的包吗?微软给我们已经写好了一个关于Identity的数据库上下文,让我们直接继承这个上下文就可以在EF Core中使用Identity。
所以我们修改一下,打开AppDbContext.cs将本来继承DbContext的,更改成IdentityDbContext
然后我们执行以下数据库迁移(EF Core 一般都是写代码,然后把代码中的类通过tools迁移到数据库,就不需要手动设置数据库了)。
在程序包控制台执行以下命令
1 Add-Migration InitUserDb -c AppDbContext -o AppMigration/User
然后执行
1 update-database
这样数据库就迁移成功了。具体操作看图
现在数据库也生成了,我们还需要在StartUp.cs将两个关系绑定起来
上面两个是孤立的,我们要增加一个方法改成下图
在认证中开启EntityFramworkStores;
现在运行一下我们的项目,能够正常运行,然后访问被保护的资源的时候会提示跳转到登录页。
具体看图
CURD用户
准备工作
微软很贴心的为我们准备了两个类,一个是负责管理用户信息的,一个是负责用户登录、登出转换权限的
- UserManager<T>
- SignInManager<T>
从字面意思也能看出来,UserManager是管理用户的,SiginManager是处理用户登录登出的,我们要在HomeController注入它们,然后使用它们,<T>是指用户类型,如果你有个类继承了IdentityUser这个类,那么你应该传入你自定义的类,否则传IdentityUser即可
代码如下
1 private readonly UserManager<IdentityUser> _userManager; 2 private readonly SignInManager<IdentityUser> _signInManager; 3 4 public HomeController(UserManager<IdentityUser> userManager,SignInManager<IdentityUser> signInManager) 5 { 6 _userManager = userManager; 7 _signInManager = signInManager; 8 }
操作示意图如下
登录处理
一个用户需要登录,我们最简单的登录需要知道用户的用户名和密码,so,我们在Home控制器下创建一个action叫做Login,指定参数username和password,如果登录成功就跳转到/Home/Secert页面
代码如下
1 [HttpPost] 2 public async Task<IActionResult> Login(string username, string password) 3 { 4 var user = await _userManager.FindByNameAsync(username).ConfigureAwait(false); 5 if (user != null) 6 { 7 var signResult = await _signInManager.PasswordSignInAsync(user, password, false, false).ConfigureAwait(false); 8 if (signResult.Succeeded) 9 { 10 return View("Secert"); 11 } 12 } 13 return View("Index"); 14 }
操作示意图如下
注册处理
一个用户需要注册,我们可能需要很多信息,但是最重要的也就是账号和密码这是我们必须要收集的,我们在Home控制器下创建一个action叫做Register,指定参数username和password,如果注册成功,那么我们就默认其已经登录,然后就跳转到/Home/Secert页面
代码如下
1 [HttpPost] 2 public async Task<IActionResult> Register(string username, string password) 3 { 4 var user = new IdentityUser 5 { 6 UserName = username, 7 Email = "lihua@qq.com", 8 }; 9 var createResult = await _userManager.CreateAsync(user, password); 10 if (createResult.Succeeded) 11 { 12 var signResult = await _signInManager.PasswordSignInAsync(user, password, false, false); 13 if (signResult.Succeeded) 14 { 15 return View("Index"); 16 } 17 else 18 { 19 return View("Index"); 20 } 21 } 22 else 23 return View("Register"); 24 }
操作示意图如下
修改用户
修改用户我们必须先拿到这个用户,然后去修改,所以肯定会传递过来一个要修改用户的主键,我们在Home控制器下创建一个action叫做Update,这里我就演示成通过用户名修改,
增加一个个人信息页面名称为Update.cshtml
代码如下
1 [Authorize] 2 [HttpGet] 3 public async Task<IActionResult> Update() 4 { 5 var user = await _userManager.GetUserAsync(HttpContext.User); 6 ViewBag.Curs = user; 7 return View("Update"); 8 }
然后增加一个页面
1 <h1>修改个人信息</h1> 2 <form formaction="/Home/Update" method="post"> 3 <input name="username" value="@ViewBag.Curs.UserName" /> 4 <input name="email" value="@ViewBag.Curs.Email" /> 5 <button>确认修改</button> 6 </form>
代码如下
1 [Authorize] 2 [HttpPost] 3 public async Task<IActionResult> Update(string username,string email) 4 { 5 var user = await _userManager.FindByNameAsync(username); 6 if (user != null) 7 { 8 user.Email = email; 9 var result = await _userManager.UpdateAsync(user); 10 if (result.Succeeded) 11 return RedirectToAction("Update"); 12 else 13 return Ok("失败"); 14 } 15 else 16 return Ok("user is not existed"); 17 }
操作示意图如下
删除用户
跟修改用户一样,肯定是拿到主键才能删除,所以我演示成username作为主键拿到用户
代码如下
1 [Authorize] 2 [HttpGet] 3 public async Task<IActionResult> RemoveUser() 4 { 5 var user = await _userManager.GetUserAsync(HttpContext.User); 6 ViewBag.Curs = user; 7 return View("Remove"); 8 }
然后增加一个页面
1 <h1>删除信息</h1> 2 <form formaction="/Home/RemoveUser" method="post"> 3 <input name="username" value="@ViewBag.Curs.UserName" /> 4 <button>确认删除</button> 5 </form>
1 [Authorize] 2 [HttpPost] 3 public async Task<IActionResult> RemoveUser(string username) 4 { 5 var user = await _userManager.FindByNameAsync(username); 6 if (user != null) 7 { 8 var result = await _userManager.DeleteAsync(user); 9 if(result.Succeeded) 10 return RedirectToAction("Index"); 11 else 12 return Ok("失败"); 13 } 14 return Ok("user is not existed"); 15 }
操作示意图如下
至此所有工作都准备好,测试一下。
可以看到,非常成功。有了.Net Core Identity配合EF Core就不需要我们自己去写一套用户管理逻辑了。
总结
- 学习了怎么使用.Net Core Identity和EF Core绑定使用
- 学习了两个基本类UserManager和SiginManager
- 学习了增删改用户信息
问题
- 如果我删除用户的时候,我把我自己删除了,然后我还能继续访问需要授权的页面吗?
- 更新个人信息的时候,更新成功了,Cookies会变吗?
- 隐藏小BUG,在注册用户的时候,会提示一个错误,并且需要修改一处代码!你能找到它吗?
又写完一篇,决定上传项目
gitee地址:https://gitee.com/JiMoKongTingChunYuWan_admin/IdentityDemo
github地址:https://github.com/Mrlie/IdentityDemo.git
下周见