系列
回顾
Hi,朋友们,我稍微整理了一下,把整个系列放到每篇文章的最前面和最后面,方便大家阅读。
我们简单回顾以下上周讲的内容
- 引入了.Net Core自带的Identity框架和EF Core
- 在SqlServer数据库中做了数据持久化
- 设计了登录/注册等功能
上次我们讲的比较简单。所以说本文就开始写如何对这些用户操作,为这些用户添加某些权限,或者使用策略限制用户访问不同的接口,达到不同的目的(这边文章选读性的)
具体要做的功能是我是下图我箭头指出的框选区域
从本篇开始,会开始精简一些机械化操作,部分操作会用图片来代替GIF图.会在调试、解决错误、运行示例的时候还是使用GIF图 同样,本文还是包含大量GIF图,请耐心等待加载
为用户添加声明
还记得我们第一章创建的Demo吗?我们当时引入Claim、Identitty等概念,仔细看一下,我们登录接口(生成Cookie接口)和我们使用.net core Identity接口有什么不同
通过比对,我们很快发现,第一章写的Claims、ClaimsIdentity、ClaimsPrincipal在.Net Core Identity的项目登录中,没有使用到。
如果我们要对一个接口进行限制,例如只有老板才能访问接口。我们在.Net Core Identity写的是完全没有办法实现这个功能的。所以我们需要改造一下。
在Identity中对用户信息操作要使用的是UserManager这个接口,顺便说一句:UserManager是SignInManager的属性,即你可以只引入SignInManager,然后使用SignInManager.UserManager的方式来访问UserManager下面的方法和属性
把代码改成下面
代码如下
1 [HttpPost] 2 public async Task<IActionResult> Login(string username, string password) 3 { 4 var user = await _userManager.FindByNameAsync(username); 5 if (user != null) 6 { 7 var signResult = await _signInManager.PasswordSignInAsync(user, password, false, false); 8 if (signResult.Succeeded) 9 { 10 var claims = new List<Claim>() 11 { 12 new Claim(ClaimTypes.Name,user.UserName), 13 new Claim("Age","15"), 14 }; 15 var r = await _signInManager.UserManager.AddClaimsAsync(user, claims); 16 if (r.Succeeded) 17 { 18 return RedirectToAction("Secert"); 19 } 20 } 21 } 22 return RedirectToAction("Register"); 23 }
从图中可以看到,我们跟第一章一样,给用户添加了Claim,然后调用了UserManager下面的AddClaimsAsync的方法,传递的参数是我们从数据库中获取出来的的User类型是IdentityUser和我们定义的Claims的集合。题外话:多个Claim组合然后找个签发机构,就会变成什么?
然后我们为接口套上一层验证机制,只需要在Authorize的标签上赋值即可。为Secect接口加上验证。并且在Secert.cshtml将该用户的Claim显示出来
代码如下
1 <h1>这需要授权访问的页面</h1> 2 3 4 @foreach (var item in User.Claims) 5 { 6 <p>@item.Type.Split('/')[item.Type.Split('/').Length - 1]:@item.Value</p> 7 }
运行结果如下
修改或删除用户的Claim
我们添加一个可以修改用户的页面,将上次的Update.cshtml页面搬出来修改一下
1 <form formaction="/Home/ModifyUserClaim" method="post"> 2 <input name="username" value="@User.Claims.Where(i=>i.Type==ClaimTypes.Name).Select(p=>p.Value).FirstOrDefault()" /> 3 <button>确认修改</button> 4 </form>
修改接口:
1 [HttpPost] 2 public async Task<IActionResult> ModifyUserClaim(string username) 3 { 4 var newclaim = new Claim(ClaimTypes.Name, "qqq"); 5 var user = await _userManager.FindByNameAsync(username); 6 await _signInManager.UserManager.ReplaceClaimAsync(user, User.Claims.Where(u=>u.Type==ClaimTypes.Name).First(), newclaim); 7 return View("Update"); 8 }
删除接口
1 [HttpPost] 2 public async Task<IActionResult> RemoveUserClaim(string username) 3 { 4 var user = await _userManager.FindByNameAsync(username); 5 await _signInManager.UserManager.RemoveClaimAsync(user, User.Claims.Where(u => u.Type == ClaimTypes.Name).First()); 6 return View("Update"); 7 }
他们分别调用了ReplaceClaimAsync和RemoveClaimAsync。其余操作没什么差异
基于策略的授权
这个应用场景非常多,日常生活中肯定会碰到多多少少需要权限访问接口的业务。例如:公司员工信息表中,有些员工填写了出生日期,有些没有,填写的人可以访问生日福利接口,没填写的人拒绝访问。又或者说:没填写的人,但是他的职位是总经理以上级别,就可以不需要填写出生日期就可以访问这个接口。等等诸如此类的权限控制。所以我们这么做
添加一个接口,和一个页面,加上Authorize(Policy ="HasBirthDay") 就是指定一个授权策略,这个授权策略名为HasBirthDay
然后添加页面
运行:很显然,会出错,提示没有找到一个名为HasBirthDay的授权策略。
我们在Startp.cs中加入这个授权策略
代码如下
1 services.AddAuthorization(config => { 2 var defaultPolicyBuilder = new AuthorizationPolicyBuilder(); 3 defaultPolicyBuilder.RequireClaim(ClaimTypes.DateOfBirth); 4 config.AddPolicy("HasBirthDay", defaultPolicyBuilder.Build()); 5 });
再次运行试试看:因为我们当面登录的用户中没有给与DateOfBirth的Claims所以会被强制跳转到拒绝授权的页面。
我们给当前的用户加上DateOfBirth,试试看能不能访问,在Login出增加一行Claims
运行:可以看到,成功运行小提示:请先清空浏览器的Cookies,否则还是会被拒绝的。
基于角色的授权
在.net中,有一个非常方便的授权模式就是基于角色的授权,例如,普通用户不能访问,管理员能访问,老板能访问,员工不能访问。基于角色的授权其实就是基于策略的授权的演化,本身属于策略的一部分。稍微改动一下代码,把Policy换成了Role 值从HasBirthDay改成了Boss
运行试试看,很显然,我们又被拒绝了。
我们在登录的时候增加以下代码:
运行:可以看到,成功运行小提示:请先清空浏览器的Cookies,否则还是会被拒绝的。
基于权限的认证显然比基于策略的使用方便很多,这也是.net 认证系统中非常闻名的一个方式,对付单个接口非常有用
好了,本期文章就写到这里我们下次再见