为了更好地理解这两个显然相似的概念之间的差异,我们可以考虑两种现实场景:
让我们尽力而为!
在本章中,我们需要前面章节中列出的所有技术要求,以及以下附加包:
和往常一样,避免直接安装它们是明智的:我们将在本章中引入它们,以便更好地将它们的用途与我们的项目联系起来。
我们不需要成为有经验的开发者,就可以认识到WWW在过去几年中发生了多大的变化;如今,每一个网站,无论其目的如何,都有越来越多或或多或少的合法兴趣跟踪其用户,让他们有机会定制导航体验、与社交网络互动、收集电子邮件地址等等。如果没有某种身份验证机制,上述任何操作都无法完成。
有几十亿个网站和服务需要身份验证才能正常工作,因为它们的大部分内容和/或意图取决于注册用户的行为:论坛、博客、购物车、基于订阅的服务,甚至维基等协作工具。
自WWW诞生以来,绝大多数认证技术都依赖于HTTP/HTTPS实现标准,它们的工作方式大致如下:
除了要求用户开发可能导致安全风险的自定义密码存储技术外,每次访问网站时被迫使用可能不同的用户名和密码可能会令人沮丧。为了克服这个问题,大量IT开发人员开始寻找一种替代方法来验证用户,这种方法可以用基于可信第三方提供商的验证协议取代基于用户名和密码的标准验证技术。
在实现第三方认证机制的第一次成功尝试中,第一次发布了“OpenTID”OpenIDAuthT1,这是一个由非营利OpenID基金会推动的开放和分散认证协议。自2005年推出以来,谷歌和StackOverflow等一些大公司迅速而热情地采用了该技术,他们最初是基于该技术的身份验证提供商。
以下是它的工作原理:
Thosewhodon'tknowwhatOAuthis,havesomepatience;we'llgettheresoonenough.
ForadditionalinformationaboutOpenID,westronglysuggestreadingthefollowingspecificationsfromtheOpenIDFoundationofficialwebsite:
当然,也有一些不利因素:
考虑到所有这些利弊,我们可以说,对于包括我们在内的小规模应用来说,依赖第三方提供商可能是一个非常省时的选择;然而,构建我们自己的账户管理系统似乎是克服上述基于治理和控制的缺陷的唯一途径,这些缺陷不可否认是由该方法带来的。
ASP.NETCore提供的身份验证模式基本上与以前版本的ASP.NET支持的模式相同:
然而,ASP.NETCore团队在过去几年中引入的实现模式正在不断发展,以匹配可用的最新安全实践。
ForadditionalinfoabouttheASP.NETCoreIdentityAPIs,checkoutthefollowingURL:
FurtherinformationaboutIdentityServercanberetrievedfromtheofficialdocumentationwebsite,whichisavailableatthefollowingURLs:
在本节中,我们将执行以下操作:
在这之后,我们还将考虑几个关于ASP.NETCore(OracleT0)任务异步编程To1T1^(Po.T2TAPOutT3)模型的词。
ASP.NETCore提供了一个统一的框架来管理和存储用户帐户,这些帐户可以在任何.NETCore应用(甚至非web应用)中轻松使用;此框架称为ASP.NETCore标识,提供了一组API,允许开发人员处理以下任务:
不用说,ASP.NETCore标识需要一个持久数据源来存储(和检索)标识数据(用户名、密码和配置文件数据),例如SQLServer数据库:正是出于这个原因,它具有与实体框架核心的内置集成机制。
ASP.NETCoreIdentityplatform强烈依赖于以下实体类型,每种实体类型代表一组特定的记录:
这些实体类型通过以下方式相互关联:
*多对多关系需要数据库中的联接表,该联接表由UserRole实体表示。
幸运的是,我们不必从头开始手动实现所有这些实体,因为ASP.NETCoreIdentity为每个实体提供了一些默认的公共语言运行时(CLR)类型:
一旦正确配置和设置了ASP.NETCore标识服务,就可以使用依赖注入(DI)将这些提供者注入到我们的.NET控制器中,就像我们使用ApplicationDbContext一样;在下一节中,我们将了解如何做到这一点。
理论讲够了,让我们把计划付诸行动吧。
在解决方案资源管理器中,右键单击WorldCities树节点,然后选择管理NuGet软件包,查找以下两个软件包,并安装它们:
或者,打开PackageManagerConsole并使用以下命令进行安装:
>Install-PackageMicrosoft.AspNetCore.Identity.EntityFrameworkCore>Install-PackageMicrosoft.AspNetCore.ApiAuthorization.IdentityServer在撰写本文时,两者的最新版本均为3.1.1;和往常一样,只要我们知道如何相应地调整代码以修复潜在的兼容性问题,我们就可以免费安装新版本。
现在我们已经安装了所需的标识库,我们需要创建一个新的ApplicationUser实体类,该类具有ASP.NETCore标识服务所需的所有功能,以便将其用于身份验证目的。幸运的是,这个包附带了一个内置的IdentityUser基类,可以用来扩展我们自己的实现,从而提供我们所需要的一切。
在解决方案资源管理器中,导航到/Data/Models/文件夹,然后创建一个新的ApplicationUser.cs类,并用以下代码填充其内容:
usingMicrosoft.AspNetCore.Identity;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;namespaceWorldCities.Data.Models{publicclassApplicationUser:IdentityUser{}}正如我们所看到的,我们不需要在那里实现任何东西,至少目前是这样:我们只需要扩展IdentityUser基类,它已经包含了我们现在所需要的一切。
为了支持.NETCore身份验证机制,我们现有的ApplicationDbContext需要从支持ASP.NETCore身份和IdentityServer的不同数据库抽象基类进行扩展。
打开/Data/ApplicationDbContext.cs文件并相应更新其内容(更新的行突出显示):
Foradditionalinformationaboutthe.NETauthenticationandauthorizationsystemforSPA,ASP.NETCoreIdentityAPI,andthe.NETCoreIdentityServer,checkoutthefollowingURL:
一旦我们保存了新的ApplicationDbContext文件,WorldCities.Tests项目中现有的CitiesController_Tests.cs类很可能会抛出一个编译器错误,如下截图所示:
原因在错误列表面板中得到了很好的解释:ApplicationDbContext的构造函数签名已更改,需要一个额外的参数,我们在此不传递。
It'sworthnotingthatthisissuedoesn'taffectourmainapplication'sControllerssinceApplicationDbContextisinjectedthroughDIthere.
要快速修复此问题,请按以下方式更新CitiesController_Tests.cs现有源代码(突出显示新的和更新的行):
usingIdentityServer4.EntityFramework.Options;//...existingcode...varstoreOptions=Options.Create(newOperationalStoreOptions());using(varcontext=newApplicationDbContext(options,storeOptions))//...existingcode...现在错误应该消失了(测试应该仍然通过)。
现在我们已经完成了所有的先决条件,我们可以打开Startup.cs文件并在ConfigureServices方法中添加以下突出显示的行,以设置ASP.NETCore标识系统所需的中间件:
//...existingcode...app.UseRouting();app.UseAuthentication();app.UseIdentityServer();app.UseAuthorization();app.UseEndpoints(endpoints=>//...existingcode...前面的代码与SPA项目的默认.NETCore标识实现非常相似。如果我们使用VisualStudio向导创建了一个新的ASP.NETCoreweb应用,选择个人用户帐户身份验证方法(请参见下面的屏幕截图),我们最终会得到相同的代码,但有一些细微的差异:
与默认实现相反,在我们的代码中,我们抓住机会覆盖了一些默认密码策略设置,以演示如何配置标识服务以更好地满足我们的需要。
让我们再看一看前面的代码,强调更改(突出显示的行):
这将赋予我们的应用一个相当高的身份验证安全级别,如果我们想让它在web上公开访问的话。不用说,我们可以根据具体需要更改这些设置;只要我们不向公众开放,开发示例可能会使用更宽松的设置。
//...existingcode...usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Identity;//...existingcode...此外,我们还需要引用用于数据模型的名称空间,因为我们现在引用的是ApplicationUser类:
//...existingcode...usingWorldCities.Data.Models;//...existingcode...既然我们已经正确地配置了Setup类,那么我们需要对IdentityServer进行同样的配置。
为了正确设置IdentityServer中间件,我们需要在现有appSettings.json配置文件中添加以下行(突出显示新行):
{"ConnectionStrings":{"DefaultConnection":"(yourconnnectionstring)"},"Logging":{"LogLevel":{"Default":"Information","Microsoft":"Warning","Microsoft.Hosting.Lifetime":"Information"}},"IdentityServer":{"Clients":{"WorldCities":{"Profile":"IdentityServerSPA"}},"Key":{"Type":"Development"}},"AllowedHosts":"*"}如我们所见,我们为IdentityServer添加了一个客户端,这将是我们的Angular应用。"IdentityServerSPA"配置文件表示应用类型,它在内部用于生成该类型的服务器默认值。在我们的场景中,IdentityServer作为一个单元与SPA一起托管。
以下是IdentityServer将为我们的应用类型加载的默认值:
其他可用配置文件包括以下内容:
*#更新appSettings.Development.json文件
打开appSettings.Development.json文件,添加以下内容(新行突出显示):
**在此之前,最好避免将其添加到生产设置中,以防止我们的web应用在不安全模式下运行
ForadditionalinformationabouttheIdentityServeranditsconfigurationparameters,checkoutthefollowingURL:
现在我们已经准备好创建我们的用户。
在解决方案资源管理器中,打开WorldCities项目的/Controllers/SeedController.cs文件,并使用以下代码相应地更新其内容(突出显示新的/更新的行):
现在,让我们在/Controllers/SeedController.cs文件的末尾定义以下方法,就在现有Import()方法的正下方:
//...existingcode...[HttpGet]publicasyncTask
从解决方案资源管理器中,在WorldCities.Tests项目中新建一个/SeedController_Tests.cs文件;完成后,用以下代码填充其内容:
同时,我们还很好地利用了Moq包库创建了大量mock来模拟实例化RoleManager和UserManager所需的大量参数。幸运的是,它们中的大多数都是内部对象,不需要执行我们当前的测试;对于那些需要的,我们必须创建一个真实的实例。
Forexample,forbothproviders,wewereforcedtocreatearealinstanceofUpperInvariantLookupNormalizer—whichimplementstheILookupNormalizerinterface—becauseit'sbeingusedinternallybyRoleManager(tolookupforexistingroles)aswellastheUserManager(tolookupforexistingusernames);ifwehadmockeditinstead,wewould'vehitsomenastyruntimeerrorswhiletryingtomakethesetestspass.
在这里,将RoleManager和UserManager生成逻辑移动到一个单独的helper类可能很有用,这样我们就可以在其他测试中使用它,而无需每次重复。
从解决方案资源管理器中,在WorldCities.Tests项目中新建一个IdentityHelper.cs文件;完成后,用以下代码填充其内容:
现在我们可以从SeedController调用这两个方法,通过以下方式更新其代码(更新的行突出显示):
//...existingcode...//createaRoleManagerinstancevarroleManager=IdentityHelper.GetRoleManager(newRoleStore
为此,右键单击解决方案资源管理器中的WorldCities.Test节点,然后选择运行测试。
Alternatively,justswitchtotheTestExplorerwindowandusethetopmostbuttonstorunthetestsfromthere.
如果我们做的一切都正确,我们应该能够看到我们的CreateDefaultUsers()测试失败,就像下面的屏幕截图:
就这样,;我们现在要做的就是实现SeedController的CreateDefaultUsers()方法,使前面的测试通过。
在/Controllers/SeedController.cs文件末尾,在现有Import()方法的正下方添加以下方法:
TheAdministratorandRegisteredUserroleswejustimplementedherewillbethecoreofourauthorizationmechanism;allofouruserswillbeassignedtoatleastoneofthem.NotehowweassignedbothofthemtotheAdminusertomakethemabletodoeverythingastandardusercando,plusmore:alltheotherusersonlyhavethelatter,sothey'llbeunabletoperformanyadministrative-leveltask—aslongasthey'renotprovidedwiththeAdministratorrole.
在继续之前,值得注意的是,Email和UserName字段都使用了用户的电子邮件地址。我们这样做是有意的,因为ASP.NETCore标识系统中的这两个字段在默认情况下可以互换使用:每当我们使用默认API添加用户时,提供的Email也会保存在UserName字段中,即使它们是AspNetUsers数据库表中的两个独立字段。虽然这种行为可以更改,但我们将坚持默认设置,这样我们就可以在整个ASP.NET标识系统中使用默认设置,而无需更改它们。
现在我们已经实现了测试,我们可以重新运行CreateDefaultUsers()测试并查看它是否通过。通常,我们可以通过右键单击解决方案资源管理器中的WorldCities.Test根节点并选择运行测试,或者从测试资源管理器面板中选择运行测试。
如果我们做的每件事都是正确的,我们会看到这样的情况:
就这样,;现在我们终于完成了项目类的更新。
下面是从前面代码中提取的await用法示例:
await_userManager.AddToRoleAsync(user_Admin,role_RegisteredUser);顾名思义,await关键字等待异步任务完成后再继续。值得注意的是,这样的表达式不会阻塞它正在执行的线程;相反,它会导致编译器注册async方法的其余部分作为等待任务的延续,从而将线程控制返回给调用方。最后,当任务完成时,它会调用其continuation,从而在停止的地方继续执行async方法。
That'sthereasonwhytheawaitkeywordcanonlybeusedwithinasyncmethods;asamatteroffact,theprecedinglogicrequiresthecallertobeasyncaswell,otherwise,itwouldn'twork.
或者,我们可以使用Wait()方法,方法如下:
_userManager.AddToRoleAsync(user_Admin,role_RegisteredUser).Wait();然而,我们没有这样做是有充分理由的。与await关键字相反,它告诉编译器异步等待异步任务完成,无参数的Wait()方法将阻塞调用线程,直到异步任务完成执行;因此,调用线程将无条件地等待任务完成。
在ASP.NET中使用调用异步任务的同步方法时,我们应该了解的第一件事是,当顶级方法等待任务时,其当前执行上下文将被阻止,直到任务完成。除非上下文一次只允许运行一个线程,否则这不会成为问题,这正是AspNetSynchronizationContext的情况。如果我们结合这两件事,我们很容易看到阻塞一个async方法(即返回异步任务的方法)将使我们的应用面临死锁的高风险。
从软件开发的Angular来看,deadlock是一种可怕的情况,每当进程或线程无限期进入等待状态时就会发生,通常是因为它等待的资源被另一个等待的进程占用。在任何旧的ASP.NETweb应用中,每次阻止任务时,我们都会面临死锁,这仅仅是因为为了完成任务,该任务将需要调用方法的相同执行上下文,在任务完成之前,调用方法会一直阻止该上下文!
幸运的是,我们这里没有使用传统的ASP.NET;我们使用的是.NETCore,其中基于SynchronizationContext的传统ASP.NET模式已被一种基于多功能、死锁弹性线程池的无上下文方法所取代。
这基本上意味着使用Wait()方法阻塞调用线程不再有问题;因此,如果我们用它切换await关键字,我们的方法仍然可以正常运行并完成。然而,通过这样做,我们基本上会使用同步代码来执行异步操作,这通常被认为是一种不好的做法;此外,我们将失去异步编程带来的所有好处,如性能和可伸缩性。
出于所有这些原因,等待方法无疑是实现这一目标的途径。
Foradditionalinformationregardingthreads,asynctasksawaits,andasynchronousprogramminginASP.NET,wehighlyrecommendcheckingouttheoutstandingarticleswrittenbyStephenClearyonthetopic,whichwillgreatlyhelpinunderstandingsomeofthemosttrickyandcomplexscenariosthatwecouldfacewhendevelopingwiththesetechnologies.Someofthemwerewrittenawhileago,yettheyneverreallyage:
下面列出了我们在本节中要做的事情:
让我们开始工作吧。
我们需要做的第一件事是向我们的数据模型添加一个新的迁移,以反映我们通过扩展ApplicationDbContext类实现的更改。
为此,请打开命令行或PowerShell提示符并转到WorldCities项目的根文件夹,然后编写以下内容:
dotnetefmigrationsadd"Identity"-o"Data\Migrations"然后应将新的迁移添加到项目中,如以下屏幕截图所示:
新的迁移文件将在\Data\Migrations\文件夹中自动生成。
Thosewhoexperienceissueswhilecreatingmigrationscantrytoclearthe\Data\Migrations\folderbeforerunningtheprecedingdotnet-efcommand.
ForadditionalinformationregardingEFCoremigrations,andhowtotroubleshootthem,checkoutthefollowingguide:
接下来要做的是将新的迁移应用到我们的数据库中。我们可以在两个选项中进行选择:
事实上,EFCore迁移功能的全部目的是提供一种方法,在保留数据库中现有数据的同时,增量更新数据库模式;正是出于这个原因,我们将遵循前一条道路
Beforeapplyingmigrations,it'salwaysadvisabletoperformafulldatabasebackup;thisadviceisparticularlyimportantwhendealingwithproductionenvironments.ForsmalldatabasessuchastheonecurrentlyusedbyourWorldCitieswebapp,itwouldtakeafewseconds.
要将迁移应用于现有数据库架构而不丢失现有数据,请从WorldCities项目的根文件夹运行以下命令:
如果一切顺利,我们应该能够看到新的身份表以及现有的Cities和Countries表:
虽然执行数据库删除和重新创建不是建议的方法,特别是考虑到我们采用了迁移模式,以避免出现这种情况,但只要我们在迁移之前完全备份数据,并且,最重要的是,知道如何在事后恢复一切。
Althoughitmightseemahorriblewaytofixthings,that'sdefinitelynotthecase;we'restillinthedevelopmentphase,hencewecandefinitelyallowafulldatabaserefresh.
如果我们选择这条路线,下面是要使用的dotnetef控制台命令:
>dotnetefdatabasedrop>dotnetefdatabaseupdatedrop命令应在继续之前请求确认是/否;当它发生时,点击Y键,让它发生。当删除和更新任务都完成后,我们可以在调试模式下运行我们的项目,并访问SeedController的Import()方法;完成后,我们应该有一个支持ASP.NETCore标识的更新数据库。
不管我们选择哪个选项来更新数据库,我们现在都必须重新填充它。
让SeedController的CreateDefaultUsers()方法发挥它的魔力。
完成后,我们应该能够看到以下JSON响应:
这个输出已经告诉我们,前两个用户已经创建并存储在我们的数据模型中。但是,我们可以通过使用SQLServerManagementStudio工具连接到我们的数据库并查看dbo.AspNetUsers表来确认这一点(请参见以下屏幕截图):
如我们所见,我们使用以下T-SQL查询来检查现有用户和角色:
SELECT*FROM[WorldCities].[dbo].[AspNetUsers];SELECT*FROM[WorldCities].[dbo].[AspNetRoles];答对了我们的ASP.NETCore标识系统实现已启动、运行,并与我们的数据模型完全集成;现在我们只需要在控制器中实现它,并将其与Angular客户端应用连接起来。
从几年前开始,最常见和传统的方法就是使用基于内存、基于磁盘或外部会话管理器将数据存储在服务器上。可以使用客户端通过身份验证响应(通常在会话cookie中)接收的唯一ID检索每个会话,该ID将在每次后续请求时传输到服务器。
下面是一个简要的图表,显示了基于会话的身份验证流程:
这仍然是大多数web应用使用的一种非常常见的技术。采用这种方法没有什么错,只要我们对其广泛承认的缺点感到满意,例如:
随着这些问题多年来的出现,毫无疑问,大多数分析师和开发人员已经投入了大量精力来找出不同的方法。
在过去几年中,基于令牌的身份验证越来越多地被单页应用(SPA)和移动应用所采用,原因无可否认,我们将在这里简要总结。
基于会话的身份验证和基于令牌的身份验证之间最重要的区别在于后者是无状态,这意味着我们不会在服务器内存、数据库、会话提供程序或任何类型的其他数据容器上存储任何用户特定的信息。
这一方面解决了我们前面指出的基于会话的身份验证的大多数缺点。我们没有会话,因此不会增加开销;我们不需要会话提供程序,因此扩展会容易得多。此外,对于支持LocalStorage的浏览器,我们甚至不会使用cookies,因此我们不会受到跨源限制性策略的阻碍,希望我们能够绕过大多数安全问题。
下面是一个典型的基于令牌的身份验证流程:
在客户机-服务器交互方面,这些步骤似乎与基于会话的身份验证流程图没有太大区别;显然,唯一的区别是我们将发布和检查令牌,而不是创建和检索会话。真正的交易正在服务器端发生(或没有发生)。我们可以立即看到,基于令牌的身份验证流不依赖于有状态会话状态服务器、服务或管理器。这将很容易转化为性能和可伸缩性方面的显著提升。
**#签名
这是大多数现代基于API的云计算和存储服务使用的方法,包括亚马逊Web服务(AWS。与基于会话和基于令牌的方法(它们依赖于理论上可以被第三方攻击者访问或暴露给第三方攻击者的传输层)不同,基于签名的身份验证使用先前共享的私钥对整个请求执行散列。这确保了没有入侵者或中间人可以充当请求用户,因为他们将无法签署请求。
这是大多数银行和金融账户使用的标准身份验证方法,可以说是最安全的方法。
实施可能会有所不同,但它始终依赖于以下基本工作流:
在回顾了所有这些身份验证方法之后,我们可以肯定地说,IdentityServer提供的基于令牌的身份验证方法对于我们的特定场景来说似乎是一个不错的选择。
我们当前的实现基于JSONWeb令牌(JWT),这是一个基于JSON的开放标准,专门为本地Web应用设计,可以使用多种语言,如.NET、Python、Java、PHP、Ruby、JavaScript/NodeJS和PERL。我们选择它是因为它正在成为令牌身份验证的事实标准,因为大多数技术都支持它。
ForadditionalinformationaboutJSONWebTokens,checkoutthefollowingURL:
为了处理基于JWT的令牌身份验证,我们需要设置我们的ASP.NET后端和Angular前端来处理所有需要的任务。
现在,这里有一些(非常)好消息:我们用来设置应用的VisualStudioAngular模板附带了对authAPI的内置支持,我们刚刚将authAPI添加到后端,最棒的是它实际上工作得非常好!
幸运的是,所有缺少的类都可以在我们当前的WorldCities项目中轻松检索和实现。。。这正是我们在本节要做的。
更具体地说,以下是我们即将完成的任务列表:
我们第三个项目的名称为AuthSample;但是,创建它所需的任务将与上次略有不同(而且肯定更容易):
更新npm包后,我们应该做的第一件事是以调试模式启动项目,并确保主页正常工作(请参见以下屏幕截图):
>npminstall>npmcacheverify这显示在以下屏幕截图中:
尽管VisualStudio应该在我们更新磁盘上的package.json文件后立即自动更新npm包,但有时自动更新过程无法正常工作:当发生这种情况时,从命令行手动执行前面的npm命令是解决此类问题的方便方法。
Theoidc-clientlibraryisanopensourcesolutionthatprovidesOIDCandOAuth2protocolsupportforclient-side,browser-basedJavaScriptclientapplications,includingusersessionsupportandaccesstokenmanagement;itsnpmpackagereferenceisalreadypresentinthepackage.jsonfileofourWorldCitiesapp,thereforewewon'thavetomanuallyaddit.
Foradditionalinfoabouttheoidc-clientlibrary,checkoutthefollowingURL:
然而,还有一个当时不存在的额外文件夹:我们谈论的是/ClientApp/src/app/authorization-api/文件夹,它基本上包含了我们当时错过的所有内容。前端实现了.NETCore身份API和IdentityServer钩子点。
该文件夹中有许多有趣的文件和子文件夹,如以下屏幕截图所示:
得益于我们对Angulararchitecture的了解,我们可以轻松理解其中每一个的主要作用:
在前面的章节中,我们在后端和前端上定义了许多路由,以允许用户访问我们实现的各种ASP.NETCore操作方法和Angular视图。如果我们仔细想想,我们会发现所有这些路线都有一个共同的特点:任何人都可以访问它们。换句话说,任何用户都可以在我们的网络应用中自由移动:他们可以编辑城市和国家,他们可以与我们的SeedController交互以执行其数据库种子任务,等等。
路线守卫是适当执行此类要求的机制;它们可以添加到我们的路由配置中,以返回可以通过以下方式控制路由行为的值:
以下路线防护装置目前在Angular中可用:
任何一条路由都可以配置多个防护:CanDeactivate和CanActivateChild首先检查防护,从最深的子路由到最上面;紧接着,路由将检查CanActivate守卫从上到下到最深的子路由;完成后,将检查异步模块的CanLoad路由。如果其中任何一个防护返回false,导航将停止,所有未决防护将被取消。
一旦创建了防护,防护就可以从路由配置本身绑定到各种路由,路由配置本身为每种防护类型提供一个属性;如果我们查看AuthSample应用的/ClientApp/src/app/app.module.ts文件,其中配置了主路由,我们可以很容易地识别防护的路由:
//...RouterModule.forRoot([{path:'',component:HomeComponent,pathMatch:'full'},{path:'counter',component:CounterComponent},{path:'fetch-data',component:FetchDataComponent,canActivate:[AuthorizeGuard]},])//...这意味着将用户带到FetchDataComponent的fetch-data视图只能由经过身份验证的用户激活:让我们快速尝试一下,看看它是否按预期工作。
看起来路由保护正在工作:如果我们现在手动编辑/ClientApp/src/app/app.module.ts文件,从fetch-data路由中删除canActivate属性,然后重试,我们将看到我们能够访问该视图而不会出现问题:
*
...也许不是。
现在我们知道为什么我们无法从后端获取该数据;如果我们删除(或注释掉)该属性,我们最终将能够,如以下屏幕截图所示:
然而,在这样做之前,我们必须完成我们的探索之旅;在下一节中,我们将介绍另一个重要的Angular概念,我们还没有讨论过。
ForfurtherinformationaboutRouteGuardsandtheirroleintheAngularRoutingworkflow,checkoutthefollowingURLs:
拦截器是Angular的一个主要功能,因为它们可以用于许多不同的任务:它们可以检查和/或记录我们应用的HTTP流量,修改请求,缓存响应,等等;它们是集中所有这些任务的方便方法,因此我们不必在数据服务和/或各种基于HttpClient的方法调用中显式地实现它们。此外,它们还可以链接,这意味着我们可以让多个拦截器在请求/响应处理程序的前向和后向链中一起工作。
AuthorizeInterceptor类随AngularauthenticationAPI一起提供,我们正在探索的特性是许多内联注释,这些注释极大地帮助我们理解它实际上是如何工作的。
要查看实际实现,请打开/ClientApp/src/app/app.module.ts文件并查看providers部分:
//...providers:[{provide:HTTP_INTERCEPTORS,useClass:AuthorizeInterceptor,multi:true}],//...我们在前面的代码中看到的multi:true属性是必需的设置,因为HTTP_INTERCEPTORS是一个多提供者令牌,它希望注入一个多值数组,而不是单个值。
**ForadditionalinformationaboutHTTPinterceptors,takealookatthefollowingURLs:
现在让我们看看/api-authorization/文件夹中包含的各种Angular组件。
位于/ClientApp/src/api-authorization/login-menu/文件夹中的LoginMenuComponent角色将包含在NavMenuComponent(我们已经很清楚)中,以将Login和Logout动作添加到现有导航选项中。
我们可以通过打开/ClientApp/src/app/nav-menu/nav-menu.component.html文件并检查是否存在以下行来检查它:
import{Component,OnInit}from'@angular/core';import{AuthorizeService}from'../authorize.service';import{Observable}from'rxjs';import{map,tap}from'rxjs/operators';@Component({selector:'app-login-menu',templateUrl:'./login-menu.component.html',styleUrls:['./login-menu.component.css']})exportclassLoginMenuComponentimplementsOnInit{publicisAuthenticated:Observable
这两个值存储在isAuthenticated和userName局部变量中,然后模板文件使用这些变量来确定组件的行为。
//...privateasynclogin(returnUrl:string):Promise
如果我们看一下它的模板文件,这个角色将变得更加明显:
{{message|async}}
就这样。事实上,这个组件确实有非常小的模板,只是因为它会将用户重定向到一些后端页面,这些页面松散地模仿了我们的Angular组件的视觉样式(!)。It'sworthnotingthatthesebuilt-inLoginandRegistrationpagesprovidedbytheASP.NETCoreback-endcanbefullycustomizedintheirUIand/orbehaviortomakethemcompatiblewiththeAngularapp'slookandfeel:seetheInstallingtheASP.NETCoreIdentityUIpackageandCustomizingthedefaultIdentityUIsectionswithinthischapterforfurtherdetailsonhowtodothis.
Inoneofmypreviousbooks(ASP.NETCore2andAngular5),Ichosetopurposelyavoidthe.NETCoreIdentityServerandmanuallyimplementtheregistrationandloginworkflowsfromthefront-end:however,the.NETCoremixedapproachhasgreatlyimprovedinthelast2yearsandnowoffersagreatalternativetothestandard,Angular-basedimplementation,thankstoasolidandhighlyconfigurableinterface.
ThosewhoprefertousetheformermethodcantakealookattheGitHubrepositoryoftheASP.NETCore2andAngular5book,(Chapter_08onward),whichisstillfullycompatiblewiththelatestAngularversions:
要激活它,打开/ClientApp/src/api-authorization/authorize.service.ts文件,将popupDisabled内部变量值从true更改为false,如下代码所示:
//...exportclassAuthorizeService{//Bydefaultpopupsaredisabledbecausetheydon'tworkproperly//onEdge.Ifyouwanttoenablepopupauthenticationsimplyset//thisflagtofalse.privatepopUpDisabled=false;privateuserManager:UserManager;privateuserSubject:BehaviorSubject
LogoutComponent是LoginComponent的对应项,因为它处理断开用户连接并将他们带回主页的任务。
这里没什么好说的,因为它的工作方式与它的兄弟类似,将用户重定向到.NETCoreIdentitysystem端点URI,然后使用returnUrl参数将其带回Angular应用。主要区别在于这次没有后端页面,因为注销工作流不需要用户界面。
点击F5以调试模式运行项目,然后导航至注册视图:插入有效电子邮件,密码与所需密码强度设置相匹配,然后点击注册按钮。
*一旦我们这样做,我们就会看到以下信息:
单击确认链接创建帐户,然后等待重新加载整个页面。
However,whenwecomparedtheprosandconsoftheNativeWebApplication,SPAandProgressiveWebApplicationapproaches,wetoldourselvesthatwewouldhavedefinitelyadoptedsomestrategicHTTPround-tripsand/orotherredirecttechniqueswheneverwecoulduseamicroservicetoliftoffsomeworkloadfromourapp;that'spreciselywhatwearedoingrightnow.
一旦完成,我们将受到以下屏幕的欢迎:
完成后,我们需要将新的前端功能与现有代码集成。
我们要修改的第一个文件是/ClientApp/src/api-authorization/api-authorization.constants.ts文件,它在其内容的第一行包含对应用名称的文字引用:
exportconstApplicationName='AuthSample';//...将'AuthSample'更改为'WorldCities',保持文件的其余部分不变。
现在我们需要将LoginMenuComponent集成到我们现有的NavMenuComponent,就像在AuthSample应用中一样。
打开/ClientApp/src/app/nav-menu/nav-menu.component.html模板文件,并在其内容中添加对菜单的引用:
让我们从导入OidcConfigurationController开始。AuthSample项目附带一个专用的.NETCoreAPI控制器,以提供URI端点,该端点将为客户端需要使用的OIDC配置参数提供服务。
将AuthSample项目的/Controllers/OidcConfigurationController.cs文件复制到WorldCities项目的/Controllers/文件夹中,然后打开复制的文件并相应更改其名称空间:
//...namespaceAuthSample.Controllers//...将AuthSample.Controllers更改为WorldCities.Controllers并继续。
在解决方案资源管理器中,右键单击WorldCities树节点,然后选择MManageNuGetpackages,查找以下软件包并安装:
Microsoft.AspNetCore.Identity.UI或者,打开PackageManager控制台并使用以下命令进行安装:
>Install-PackageMicrosoft.AspNetCore.Identity.UI在撰写本文时,该软件包的最新可用版本为3.1.1;和往常一样,只要我们知道如何相应地调整代码以修复潜在的兼容性问题,我们就可以免费安装新版本。
Generated(andmodified)codewillautomaticallytakeprecedenceoverthedefaultcodeintheIdentityRCL.
要完全控制UI而不使用默认RCL,请参阅以下指南:
现在我们(内部)正在使用一些Razor页面,我们需要将它们映射到后端路由系统,否则,.NETCore应用不会将HTTP请求转发给它们。
为此,打开WorldCities项目的Startup.cs文件,并在EndpointMiddleware配置块中添加以下高亮显示的行:
usingMicrosoft.AspNetCore.Authorization;//...[Authorize][HttpPut("{id}")]publicasyncTask
Ifwedidn'tchangethematthetime,thesamplevaluesthatweusedinSeedControllershouldbethefollowing:email:user@email.comandpassword:MySecr3t$.
如果我们做的每件事都正确,我们将看到如下屏幕截图所示的屏幕:
事实上,我们仍然缺少一些关键功能,例如:
NowthatwehaveimplementedtheASP.NETCoreIdentityservices,implementinganemailsendertotakecareoftheprecedingfeatureswouldbearathereasytask,especiallyifweuseanexternalservicesuchasSendGrid.
Foradditionalinformation,checkoutthefollowingguide:
然而,对于我们的示例应用来说,这绝对足够了;我们准备好进入下一个话题。
为了能够使用它,我们将所需的包添加到我们的项目中,并进行适当配置所需的操作,例如在我们的Startup和ApplicationDbContext类中执行一些更新,并创建一个新的ApplicationUser实体;在实现了所有必需的更改之后,我们添加了一个新的EF核心迁移来相应地更新我们的数据库。
我们简要列举了目前可用的各种基于web的身份验证方法:会话、令牌、签名和各种双因素策略。经过仔细考虑,我们选择了使用JWT的基于令牌的方法,JWT是IdentityServer默认为SPA客户端提供的,它是任何前端框架的可靠且众所周知的标准。
我们已经准备好切换到下一个主题渐进式web应用,这将使我们在下一章中忙个不停。