ASP.NET核心授权实验室
这是为ASP.NET核心授权实验室的过程,现在已更新为ASP.NET Core 2.1和VS2017。 (如果您仍在使用1.x,则实验室的较旧版本可在Core1x分支中使用。)
该实验室使用模型视图控制器模板,因为这是每个人到现在一直在使用的,这是绝大多数人的最熟悉的起点。
官方授权文档在https://docs.a*s**p.net/en/latest/security/authorization/authorization/index.html。
提示:在每个阶段停止完成应用程序时,请始终关闭浏览器以清除身份cookie。
步骤0:准备
创建一个新的空白,ASP.NET项目。
- 文件>新项目> Visual C#> .NET Core
- 选择ASP.NET Core Web应用程序,为项目提供
AuthorizationLab的名称,然后单击“确定”。 - 选择空模板,请确保在项目类型上方的下拉列表中选择ASP.NET Core 2.1,然后单击“确定”。
将MVC添加到应用程序中。
- 编辑
Startup.cs并添加services.AddMvc();到ConfigureServices()方法的顶部; - 编辑
Configure()方法,删除现有代码。 - 在现在的空
Configure();将以下代码添加到设置MVC默认路由;
app . UseMvc ( routes => { routes . MapRoute ( name : \"default\" , template : \"{controller=Home}/{action=Index}/{id?}\" ) ; } ) ;
- 创建一个
Controllers文件夹 - 使用VS Controller模板在控制器目录中创建
HomeController.cs文件,或从头开始创建它并确保从Controller继承并具有Index()方法,该方法返回一个视图,例如
using Microsoft . AspNetCore . Mvc ; namespace AuthorizationLab . Controllers { public class HomeController : Controller { public IActionResult Index ( ) { return View ( ) ; } } }
- 创建一个
Views文件夹。 - 在
Views下创建一个Home文件夹。 - 在
Views\\Home文件夹中创建一个Index.cshtml文件,然后对其进行编辑以包含Hello World。 - 运行您的应用程序并确保您看到Hello World。
步骤1:设置身份验证
- 在ASP.NET Core 2.1中,
Microsoft.AspNetCore.AppMeta软件包包含所有身份验证和授权软件包,因此您无需添加任何额外的软件包或参考。 - Open
startup.cs - 添加
app.UseAuthentication();在Configure()方法的顶部。 - 通过将以下内容添加到
ConfigureServices()方法的顶部,将Cookie Mifferware添加到身份验证服务中。
services . AddAuthentication ( CookieAuthenticationDefaults . AuthenticationScheme ) . AddCookie ( CookieAuthenticationDefaults . AuthenticationScheme , options => { options . LoginPath = new PathString ( \"/Account/Login/\" ) ; options . AccessDeniedPath = new PathString ( \"/Account/Forbidden/\" ) ; } ) ;
- 编辑主控制器,然后将
[Authorize]属性添加到控制器。 - 运行项目和恐慌。您会遇到404错误。您没有登录页面。
- 现在,在
Controllers文件夹中创建Account控制器,AccountController.cs。创建一个Login()操作和Forbidden()操作。
using Microsoft . AspNetCore . Mvc ; namespace AuthorizationLab . Controllers { public class AccountController : Controller { public IActionResult Login ( ) { return View ( ) ; } public IActionResult Forbidden ( ) { return View ( ) ; } } }
- 在
Views文件夹下创建一个Account文件夹,并为操作创建相应的视图,Login.cshtml和Forbidden.cshtml。 - 在每个视图中添加一些文本,以便您可以判断要显示哪个视图,然后运行您的项目。您会看到您最终进入登录视图。
- 返回
Account控制器。更改Login操作以创建Principal,并使用以下代码坚持下去。这将创建用户信息并将其放入cookie中。这就假装基于表单的登录系统通常会发生的事情。
public async Task < IActionResult > Login ( string returnUrl = null ) { const string Issuer = \"https://*cont*os*o.com\" ; var claims = new List < Claim > ( ) ; claims . Add ( new Claim ( ClaimTypes . Name , \"barry\" , ClaimValueTypes . String , Issuer ) ) ; var userIdentity = new ClaimsIdentity ( \"SuperSecureLogin\" ) ; userIdentity . AddClaims ( claims ) ; var userPrincipal = new ClaimsPrincipal ( userIdentity ) ; await HttpContext . SignInAsync ( CookieAuthenticationDefaults . AuthenticationScheme , userPrincipal , new AuthenticationProperties { ExpiresUtc = DateTime . UtcNow . AddMinutes ( 20 ) , IsPersistent = false , AllowRefresh = false } ) ; return RedirectToLocal ( returnUrl ) ; } private IActionResult RedirectToLocal ( string returnUrl ) { if ( Url . IsLocalUrl ( returnUrl ) ) { return Redirect ( returnUrl ) ; } else { return RedirectToAction ( \"Index\" , \"Home\" ) ; } }
- 最后,编辑
Home\\Index视图以显示身份的名称声明。用以下代码替换该视图的内容。此代码故意有些复杂,因为用户主体可以包含多个身份验证的身份。这很少发生,坦率地说,您是否已经写了代码来执行此操作,但是您应该知道此边缘情况。
@using System.Security.Claims;
@if (!User.Identities.Any(u => u.IsAuthenticated))
{
<h1>Hello World</h1>
}
else
{
<h1>Hello @User.Identities.First(
u => u.IsAuthenticated &&
u.HasClaim(c => c.Type == ClaimTypes.Name)).FindFirst(ClaimTypes.Name).Value</h1>
}
- 运行项目,看看会发生什么。您应该看到“你好巴里”。代码
claims.Add(new Claim(ClaimTypes.Name, \"barry\", ClaimValueTypes.String, Issuer));帐户控制器中的Login()方法内部是设置姓名声明的内容,并且是视图所显示的。
请记住要关闭浏览器以清除身份cookie,然后再进入下一步。
步骤2:授权所有事情
- 首先从
Home控制器中删除Authorize属性。 - 在
Startup.cs中的ConfigureServices()中更改AddMvc()调用,以将默认授权策略添加到MVC配置中。
services . AddMvc ( config => { var policy = new AuthorizationPolicyBuilder ( ) . RequireAuthenticatedUser ( ) . Build ( ) ; config . Filters . Add ( new AuthorizeFilter ( policy ) ) ; } ) ;
- 跑步并观察一切爆炸成无限的重定向循环。为什么?您已经使每个页面都需要身份验证。这甚至包括登录页面。
- 现在,将
[AllowAnonymous]属性添加到Account控制器中,再次运行并查看用户已登录。AllowAnonymous允许您将控制器或操作方法标记为不需要身份验证,即使您需要在其他地方进行身份验证。
请记住要关闭浏览器以清除身份cookie,然后再进入下一步。
步骤3:角色
- 转到
Home控制器,并为控制器或索引操作方法添加具有角色需求的Authorize属性;
[ Authorize ( Roles = \"Administrator\" ) ]
- 运行应用程序并确认您已重定向到
Forbidden视图。发生这种情况是因为您具有身份,但这不是管理员角色的一部分。 - 关闭浏览器以清除身份曲奇。
- 返回到
AccountController并添加第二个claims.Add()行,如下所示。这将管理员价值的角色主张添加到已发行的身份。
claims . Add ( new Claim ( ClaimTypes . Role , \"Administrator\" , ClaimValueTypes . String , Issuer ) ) ;
- 运行应用程序并确认您已登录。
请记住要关闭浏览器以清除身份cookie,然后再进入下一步。
步骤4:简单政策
- 返回到
Startup.cs,然后在ConfigureServices()services.AddAuthentication())调用。 - After
services.AddAuthentication()将调用添加到services.AddAuthorization()并创建一个简单的策略,如下所示。
services . AddAuthorization ( options => { options . AddPolicy ( \"AdministratorOnly\" , policy => policy . RequireRole ( \"Administrator\" ) ) ; } ) ;
- 此策略等同于您在步骤3中
Authorize属性参数中使用的角色检查。 - 现在,更改家庭控制器
Authorize属性需要策略,而不是使用角色参数。
[ Authorize ( Policy = \"AdministratorOnly\" ) ]
- 运行该应用程序并确认您仍然可以看到主页。所改变的只是您指定要求的方式。您没有将角色名称嵌入到属性中,而是写了一个指定角色名称的策略。
- 关闭浏览器以清除您的身份cookie。
- 现在添加第二个政策,这次需要索赔。
services . AddAuthorization ( options => { options . AddPolicy ( \"AdministratorOnly\" , policy => policy . RequireRole ( \"Administrator\" ) ) ; options . AddPolicy ( \"EmployeeId\" , policy => policy . RequireClaim ( \"EmployeeId\" ) ) ; } ) ;
- 向
Account控制器中Login操作发出的身份添加合适的索赔。
claims . Add ( new Claim ( \"EmployeeId\" , string . Empty , ClaimValueTypes . String , Issuer ) ) ;
- 使用新的策略名称将新的
Authorize属性添加到家庭控制器。
[ Authorize ( Policy = \"EmployeeId\" ) ]
- 运行该应用程序并确保您可以查看主页。
- 不过,这是一项相当无用的检查。索赔由索赔名称和索赔价值组成。您确实想检查值,而不仅仅是索赔的存在。幸运的是,有一个参数。更改
EmployeeId政策以需要许多值之一;
options . AddPolicy ( \"EmployeeId\" , policy => policy . RequireClaim ( \"EmployeeId\" , \"123\" , \"456\" ) ) ;
- 再次运行该应用程序,空索赔将被拒绝,您最终将在禁止页面上。
- 关闭您的浏览器以清除身份cookie。
- 更改身份发行代码以对
Login操作具有适当的索赔价值,如下所示,然后重试。
claims . Add ( new Claim ( \"EmployeeId\" , \"123\" , ClaimValueTypes . String , Issuer ) ) ;
如果保单有多个索赔要求,则必须满足所有索赔要求才能获得成功。
请记住要关闭浏览器以清除身份cookie,然后再进入下一步。
步骤5:基于代码的政策
基于代码的策略由要求组成,实施IAuthorizationRequirement和用于要求的处理程序,实施AuthorizationHandler<T> t是要求。
- 在
Account控制器中的Login操作中,向用户本金添加出生日期。
claims . Add ( new Claim ( ClaimTypes . DateOfBirth , \"1970-06-08\" , ClaimValueTypes . Date ) ) ;
- 现在创建自定义要求和处理程序类;称为MuniminAgeRequrement。为了简单起见,我们将在这里使用单个类以及处理程序。
using System ; using System . Security . Claims ; using System . Threading . Tasks ; using Microsoft . AspNetCore . Authorization ; namespace AuthorizationLab { public class MinimumAgeRequirement : AuthorizationHandler < MinimumAgeRequirement > , IAuthorizationRequirement { int _minimumAge ; public MinimumAgeRequirement ( int minimumAge ) { _minimumAge = minimumAge ; } protected override Task HandleRequirementAsync ( AuthorizationHandlerContext context , MinimumAgeRequirement requirement ) { if ( ! context . User . HasClaim ( c => c . Type == ClaimTypes . DateOfBirth ) ) { return Task . CompletedTask ; } var dateOfBirth = Convert . ToDateTime ( context . User . FindFirst ( c => c . Type == ClaimTypes . DateOfBirth ) . Value ) ; int calculatedAge = DateTime . Today . Year - dateOfBirth . Year ; if ( dateOfBirth > DateTime . Today . AddYears ( - calculatedAge ) ) { calculatedAge -- ; } if ( calculatedAge >= _minimumAge ) { context . Succeed ( requirement ) ; } return Task . CompletedTask ; } } }
- 在addauthorization函数中创建Over21策略;
options . AddPolicy ( \"Over21Only\" , policy => policy . Requirements . Add ( new MinimumAgeRequirement ( 21 ) ) ) ;
- 使用
Authorize属性将其应用于Home控制器。 - 运行该应用程序并确保您可以查看主页。
- 实验出生价值日期(例如,成为去年的一年)以使授权失败。在继续前进之前,不要忘记将其设置回传递值。
请记住要关闭浏览器以清除身份cookie,然后再进入下一步。
步骤6:多个处理程序需要
您可能已经注意到处理程序的返回,什么都没有(严格来说Task.CompletedTask; 。处理人员通过调用context.Succeed(requirement); 。您可能会问自己是否有context.Succeed()有context.Fail() ?有,但是如果不满足您的要求,您根本不应该触摸上下文。现在您可能会问为什么不呢?出色地 …
有时,您可能需要多个处理程序来获得授权要求,例如,有多种方法可以满足要求。微软的办公室门带有您的Microsoft徽章,但是在您忘记徽章的日子里,您可以去接待处并获得临时通行证,接待员将让您穿过大门。因此,有两种方法可以满足单一条目要求。在ASP.NET核心授权模型中,这将作为单个要求的两个处理程序实现。
- 首先,写一个新的
IAuthorizationRequirement,OfficeEntryRequirement。
using Microsoft . AspNetCore . Authorization ; namespace AuthorizationLab { public class OfficeEntryRequirement : IAuthorizationRequirement { } }
- 现在编写一个
AuthorizationHandler,检查当前身份是否具有由雇主HasBadgeHandler发出的徽章号索赔。
using Microsoft . AspNetCore . Authorization ; namespace AuthorizationLab { public class HasBadgeHandler : AuthorizationHandler < OfficeEntryRequirement > { protected override Task HandleRequirementAsync ( AuthorizationHandlerContext context , OfficeEntryRequirement requirement ) { if ( ! context . User . HasClaim ( c => c . Type == \"BadgeNumber\" && c . Issuer == \"https://*cont*os*o.com\" ) ) { return Task . CompletedTask ; } context . Succeed ( requirement ) ; return Task . CompletedTask ; } } }
这会照顾记得合适公司发行的徽章的人(在所有多家公司都有入场卡之后,您想检查该卡是否由您期望的公司发行。 Claims类具有发行人的财产,详细介绍了发出索赔的人,因此在我们的情况下,这是发行徽章的人)。
但是,那些忘记并拥有临时徽章的人呢?您可以将其全部放入一个处理程序中,但是处理程序和要求是可以重复使用的。您可以将上面显示的HasBadgeHandler用于其他事项,而不仅仅是办公室条目(例如,Microsoft代码签名基础架构需要我们的Office Badge的智能卡来触发作业)。
- 为了应付临时徽章,撰写另一个
AuthorizationHandler,HasTemporaryPassHandler
using System ; using Microsoft . AspNetCore . Authorization ; namespace AuthorizationLab { public class HasTemporaryPassHandler : AuthorizationHandler < OfficeEntryRequirement > { protected override Task HandleRequirementAsync ( AuthorizationHandlerContext context , OfficeEntryRequirement requirement ) { if ( ! context . User . HasClaim ( c => c . Type == \"TemporaryBadgeExpiry\" && c . Issuer == \"https://*cont*os*o.com\" ) ) { return Task . CompletedTask ; } var temporaryBadgeExpiry = Convert . ToDateTime ( context . User . FindFirst ( c => c . Type == \"TemporaryBadgeExpiry\" && c . Issuer == \"https://*cont*os*o.com\" ) . Value ) ; if ( temporaryBadgeExpiry > DateTime . Now ) { context . Succeed ( requirement ) ; } return Task . CompletedTask ; } } }
请注意,没有处理程序调用Context.Fail()。 context.fail()有时会有授权无法继续,例如,即使还有另一个处理程序,“我的整个用户数据库都在纵火。”或“我正在寻找的用户刚刚被阻止,但其他后端系统可能尚未更新。”
- 接下来,为要求创建一个策略,在授权配置内的
Startup.cs中的ConfigureServices()中注册它。
options . AddPolicy ( \"BuildingEntry\" , policy => policy . Requirements . Add ( new OfficeEntryRequirement ( ) ) ) ;
- 返回到
Account控制器的Login方法,并添加合适的徽章ID声明。
claims . Add ( new Claim ( \"BadgeNumber\" , \"123456\" , ClaimValueTypes . String , Issuer ) ) ;
- 最后,将创建的策略应用于使用
Authorize属性中的Home控制器中的Index视图。
[ Authorize ( Policy = \"BuildingEntry\" ) ]
- 运行该应用程序,哦,亲爱的,我们被禁止禁止。为什么?
处理程序保存在ASP.NET DI容器中。在以前的样本中,我们将要求和处理程序组合在一个类中,因此授权系统知道它,而无需在DI中手动注册。现在,我们有单独的处理程序需要在DI容器中注册它们,然后才能找到它们。
- 打开
Startup.cs和InsideConfigureServices()通过将以下内容添加到ConfigureServices()方法的底部,在DI容器中注册处理程序。请注意,它们不必是单例,您可以使用DI系统将构造函数参数注入处理程序,因此,例如,如果您要注入EF存储库,则可能需要将处理程序添加为scopepter scopep of Request。
services . AddSingleton < IAuthorizationHandler , HasBadgeHandler > ( ) ; services . AddSingleton < IAuthorizationHandler , HasTemporaryPassHandler > ( ) ;
- 再次运行该应用程序,您可以看到授权工作。
- 尝试评论BadGenumber主张并用临时性的呼吸道主张代替,并记住每次关闭浏览器以清除身份cookie,以便将其重新创建您的新主张。
claims . Add ( new Claim ( \"TemporaryBadgeExpiry\" , DateTime . Now . AddDays ( 1 ) . ToString ( ) , ClaimValueTypes . String , Issuer ) ) ;
- 运行该应用程序,并且您仍然获得授权,因为现在临时徽章的处理程序满足了建筑物的要求。
- 更改临时徽章索赔,以使其过期;在运行新代码之前,请记住关闭浏览器以清除身份cookie。
claims . Add ( new Claim ( \"TemporaryBadgeExpiry\" , DateTime . Now . AddDays ( - 1 ) . ToString ( ) , ClaimValueTypes . String , Issuer ) ) ;
- 重新运行该应用程序,您会看到您被禁止。
- 删除BadGenumber索赔代码的临时徽章索赔,因此您要重新获得授权,关闭浏览器以清除身份cookie并重新运行该应用程序,以确保您不再被禁止。
步骤7:基于资源的要求
到目前为止,我们涵盖了仅基于用户身份的要求。但是,经常授权需要访问资源。例如,文档类可能有作者,只有作者才能编辑文档,而其他作者可以查看它。
- 创建一个资源类,具有
intID属性的Document和string作者属性。
namespace AuthorizationLab { public class Document { public int Id { get ; set ; } public string Author { get ; set ; } } }
- 为文档类创建一个存储库接口,
IDocumentRepository
using System . Collections . Generic ;
