IdentityServer4是ASP.NETCore2的OpenIDConnect和OAuth2.0框架。
它在您的应用程序中启用以下功能:
为各种类型的客户端发出API访问令牌,例如服务器到服务器,Web应用程序,SPA和本机/移动应用程序。
支持AzureActiveDirectory,Google,Facebook等外部身份提供商。这可以保护您的应用程序免受如何连接到这些外部提供商的详细信息的影响。
最重要的部分-IdentityServer的许多方面都可以根据您的需求进行定制。由于IdentityServer是一个框架而不是盒装产品或SaaS,因此您可以编写代码以使系统适应您的方案。
大多数现代应用程序或多或少看起来像这样:
最常见的互动是:
将这些基本安全功能外包给安全令牌服务可防止在这些应用程序和端点之间复制该功能。
重构应用程序以支持安全令牌服务会产生以下体系结构和协议:
这种设计将安全问题分为两部分:
当应用程序需要知道当前用户的身份时,需要进行身份验证。通常,这些应用程序代表该用户管理数据,并且需要确保该用户只能访问允许的数据。最常见的例子是(经典)Web应用程序-但是基于本机和JS的应用程序也需要身份验证。
最常见的身份验证协议是SAML2p,WS-Federation和OpenIDConnect-SAML2p是最受欢迎和最广泛部署的。
OpenIDConnect是三者中的最新产品,但被认为是未来,因为它具有最大的现代应用潜力。它是从一开始就为移动应用场景而构建的,旨在实现API友好。
应用程序有两种与API通信的基本方式-使用应用程序标识或委派用户的标识。有时两种方法都需要结合起来。
OpenIDConnect和OAuth2.0非常相似-事实上,OpenIDConnect是OAuth2.0之上的扩展。两个基本的安全问题,即身份验证和API访问,被合并为一个协议-通常只需要一次往返安全令牌服务。
我们相信,OpenIDConnect和OAuth2.0的结合是在可预见的未来保护现代应用程序的最佳方法。IdentityServer4是这两种协议的实现,经过高度优化,可以解决当今移动,本机和Web应用程序的典型安全问题。
IdentityServer是一个中间件,可将符合规范的OpenIDConnect和OAuth2.0端点添加到任意ASP.NETCore应用程序中。
规范,文档和对象模型使用您应该注意的某些术语。
IdentityServer是OpenIDConnect提供程序-它实现OpenIDConnect和OAuth2.0协议。
但它们完全相同:一种向客户发放安全令牌的软件。
IdentityServer具有许多作业和功能-包括:
用户是使用注册客户端访问资源的人。
客户端是从IdentityServer请求令牌的软件-用于验证用户(请求身份令牌)或访问资源(请求访问令牌)。客户端必须首先向IdentityServer注册,然后才能请求令牌。
客户端的示例包括Web应用程序,本机移动或桌面应用程序,SPA,服务器进程等。
您希望使用IdentityServer保护资源-用户的身份数据或API。
每个资源都有一个唯一的名称-客户端使用此名称来指定他们希望访问哪些资源。
APIAPI资源表示客户端要调用的功能-通常建模为WebAPI,但不一定。
IdentityServer实现以下规范:
IdentityServer由许多nuget包组成。
包含核心IdentityServer对象模型,服务和中间件。仅包含对内存配置和用户存储的支持-但您可以通过配置插入对其他存储的支持。这是其他回购和包装的内容。
用于验证API中令牌的ASP.NETCore身份验证处理程序。处理程序允许在同一API中支持JWT和引用令牌。
IdentityServer的ASP.NET核心身份集成包。该软件包提供了一个简单的配置API,可以为IdentityServer用户使用ASP.NET身份管理库。
EntityFrameworkIdentityServer的核心存储实现。此程序包为IdentityServer中的配置和操作存储提供EntityFramework实现。
此外,我们将开发/临时构建发布到MyGet。如果要尝试尝试,请将以下Feed添加到VisualStudio:
我们为IdentityServer提供了多种免费和商业支持和咨询选项。
免费支持是基于社区的,并使用公共论坛
堆栈溢出
IdentityServer4在提出新问题时请使用标签
小胶质
您可以在我们的Gitter聊天室中与其他IdentityServer4用户聊天:
报告错误
训练
AdminUI,IdentityExpress和SAML2p支持
我们对社区贡献非常开放,但您应该遵循一些指导方针,以便我们可以毫不费力地处理这个问题。
最简单的贡献方式是打开一个问题并开始讨论。然后我们可以决定是否以及如何实现功能或更改。如果您应该提交带有代码更改的pull请求,请从描述开始,只进行最小的更改并提供涵盖这些更改的测试。
IdentityServer是针对ASP.NETCore2构建的,可在.NETFramework4.6.1(及更高版本)和.NETCore2(及更高版本)上运行。
请在相应的GitHub仓库中记录一个新问题:
在您提供任何代码或内容之前,您需要签署贡献者许可协议。这是一个自动过程,将在您打开拉取请求后启动。
注意
我们只接受开发分支的PR。
如果您启动贡献项目(例如,支持DatabaseX或ConfigurationStoreY),我们非常感谢。告诉我们,我们可以在我们的文档中发推文和链接。
我们通常不想拥有这些贡献库,我们已经非常忙于支持核心项目。
命名约定
截至2017年10月,IdentityServer4。*nuget名称空间保留给我们的软件包。请使用以下命名约定:
YourProjectName.IdentityServer4
要么
IdentityServer4.Contrib.YourProjectName
启动新IdentityServer项目有两种基本方法:
如果您从头开始,我们提供了几个帮助程序和内存存储,因此您不必担心从一开始就存在持久性。
如果您从ASP.NET身份开始,我们也提供了一种简单的方法来集成它。
快速入门提供了各种常见IdentityServer方案的分步说明。他们从绝对的基础开始,变得更加复杂-建议你按顺序完成它们。
屏幕截图显示了VisualStudio-但这不是必需的。
创建快速入门IdentityServer
首先创建一个新的ASP.NETCore项目。
然后选择“清空”选项。
接下来,添加IdentityServer4nuget包:
或者,您可以使用程序包管理器控制台通过运行以下命令来添加依赖项:
“安装包IdentityServer4”
IdentityServer构建编号1.x目标ASP.NETCore1.1,IdentityServer构建编号2.x目标ASP.NETCore2.0。
IdentityServer使用通常的模式为ASP.NETCore主机配置和添加服务。在ConfigureServices所需的服务中配置并添加到DI系统。在Configure中间件中添加到HTTP管道。
将Startup.cs文件修改为如下所示:
IdentityServer尚未准备好启动。我们将在以下快速入门中添加所需的服务。
默认情况下,VisualStudio使用IISExpress来托管您的Web项目。这完全没问题,除了您将无法看到控制台的实时日志输出。
IdentityServer广泛使用日志记录,而UI中的“可见”错误消息或返回给客户端是故意模糊的。
我们建议在控制台主机中运行IdentityServer。您可以通过在VisualStudio中切换启动配置文件来完成此操作。每次启动IdentityServer时也不需要启动浏览器-您也可以关闭它:
我们建议为IISExpress和自托管配置相同的端口。这样,您可以在两者之间切换,而无需修改客户端中的任何配置。
要在启动时选择控制台主机,必须在VisualStudio的启动菜单中选择它:
运行快速入门解决方案各个部分的最简单方法是将启动模式设置为“当前选择”。右键单击解决方案并选择“设置启动项目”:
通常,首先启动IdentityServer,然后启动API,然后启动客户端。如果您确实想要调试,只能在调试器中运行。否则Ctrl+F5是运行项目的最佳方式。
本快速入门介绍了使用IdentityServer保护API的最基本方案。
在这种情况下,我们将定义一个API和一个想要访问它的客户端。客户端将在IdentityServer请求访问令牌并使用它来获取对API的访问权限。
范围定义了您要保护的系统中的资源,例如API。
由于我们在本演练中使用内存配置-您只需创建一个类型的对象ApiResource并设置适当的属性即可。
将文件(例如Config.cs)添加到项目中并添加以下代码:
publicstaticIEnumerable
对于此方案,客户端将不具有交互式用户,并将使用IdentityServer的所谓客户端密钥进行身份验证。将以下代码添加到Config.cs文件中:
接下来,为您的解决方案添加API。
控制器
向API项目添加新控制器:
组态
最后一步是将身份验证服务添加到DI和身份验证中间件到管道。这些将:
将IdentityServer4.AccessTokenValidationNuGet包添加到项目中。
将Startup更新为如下所示:
就是这样,API现在受到IdentityServer的保护。
IdentityServer的令牌端点实现OAuth2.0协议,您可以使用原始HTTP来访问它。但是,我们有一个名为IdentityModel的客户端库,它将协议交互封装在一个易于使用的API中。
将IdentityModelNuGet包添加到您的应用程序。
IdentityModel包括用于发现端点的客户端库。这样您只需要知道IdentityServer的基地址-可以从元数据中读取实际的端点地址:
接下来,您可以使用该RequestClientCredentialsAsync方法为您的API请求令牌:
//requesttokenvartokenClient=newTokenClient(disco.TokenEndpoint,"client","secret");vartokenResponse=awaittokenClient.RequestClientCredentialsAsync("api1");if(tokenResponse.IsError){Console.WriteLine(tokenResponse.Error);return;}Console.WriteLine(tokenResponse.Json);注意
最后一步是调用API。
要将访问令牌发送到API,通常使用HTTPAuthorization标头。这是使用SetBearerToken扩展方法完成的:
您现在可以尝试激发错误以了解系统的行为,例如
OAuth2.0资源所有者密码授予允许客户端向令牌服务发送用户名和密码,并获取代表该用户的访问令牌。
规范建议仅对“受信任”(或遗留)应用程序使用资源所有者密码授予。一般来说,当您想要对用户进行身份验证并请求访问令牌时,通常会更好地使用其中一个交互式OpenIDConnect流程。
就像资源(也称为范围)和客户端的内存存储一样,用户也有一个。
有关如何正确存储和管理用户帐户的详细信息,请查看基于ASP.NET身份的快速入门。
首先将以下using语句添加到Config.cs文件中:
usingIdentityServer4.Test;publicstaticList
publicvoidConfigureServices(IServiceCollectionservices){//configureidentityserverwithin-memorystores,keys,clientsandscopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiResources(Config.GetApiResources()).AddInMemoryClients(Config.GetClients()).AddTestUsers(Config.GetUsers());}该AddTestUsers扩展方法做了几件事情引擎盖下
通常,您希望为资源所有者用例创建单独的客户端,将以下内容添加到客户端配置中:
IdentityModel再次TokenClient可以在这里提供帮助:
在本快速入门中,我们希望通过OpenIDConnect协议为我们的IdentityServer添加对交互式用户身份验证的支持。
一旦到位,我们将创建一个将使用IdentityServer进行身份验证的MVC应用程序。
虽然外观和精确的工作流程在每个IdentityServer实现中可能总是不同,但我们提供了一个基于MVC的示例UI,您可以将其用作起点。
或者,您可以从与IdentityServerWeb应用程序相同的目录中的命令行运行此命令,以自动执行下载:
publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();//configureidentityserverwithin-memorystores,keys,clientsandscopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiResources(Config.GetApiResources()).AddInMemoryClients(Config.GetClients()).AddTestUsers(Config.GetUsers());}Configure使用UseMvc扩展方法将MVC添加为管道中的最后一个中间件:
releaseUIrepo的分支具有与最新稳定版本匹配的UI。该dev分支与IdentityServer4的当前开发版本一起使用。如果您正在寻找特定版本的UI-请检查标签。
接下来,您将向您的解决方案添加MVC应用程序。使用ASP.NETCore“Web应用程序”(即MVC)模板。不要在向导中配置“身份验证”设置-您将在此快速入门中手动执行此操作。创建项目后,将应用程序配置为使用端口5002(有关如何执行此操作的说明,请参阅概述部分)。
要为ID连接的认证支持添加到了MVC应用程序,添加以下内容ConfigureServices中Startup:
然后AddCookie,我们使用添加可以处理cookie的处理程序。
最后,AddOpenIdConnect用于配置执行OpenIDConnect协议的处理程序。这Authority表明我们信任IdentityServer。然后我们通过ClientId。识别这个客户。SignInScheme用于在OpenIDConnect协议完成后使用cookie处理程序发出cookie。并且SaveTokens用于在cookie中保留来自IdentityServer的令牌(因为稍后将需要它们)。
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();然后要确保认证服务执行对每个请求,加入UseAuthentication到Configure中Startup:
publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");}app.UseAuthentication();app.UseStaticFiles();app.UseMvcWithDefaultRoute();}应该在管道中的MVC之前添加认证中间件。
- @foreach(varclaiminUser.Claims){
- @claim.Type
- @claim.Value }
与OAuth2.0类似,OpenIDConnect也使用范围概念。同样,范围代表您想要保护的内容以及客户想要访问的内容。与OAuth相比,OIDC中的范围不代表API,而是代表用户ID,名称或电子邮件地址等身份数据。
通过添加新助手(in)来创建对象集合,添加对标准openid(subjectid)和profile(名字,姓氏等)范围的支持:Config.cs``IdentityResource
publicstaticIEnumerable
然后,您需要将这些标识资源添加到IdentityServer配置中Startup.cs。使用AddInMemoryIdentityResources您调用的扩展方法AddIdentityServer():
publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();//configureidentityserverwithin-memorystores,keys,clientsandscopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryIdentityResources(Config.GetIdentityResources()).AddInMemoryApiResources(Config.GetApiResources()).AddInMemoryClients(Config.GetClients()).AddTestUsers(Config.GetUsers());}为OpenIDConnect隐式流添加客户端最后一步是将MVC客户端的新配置条目添加到IdentityServer。
基于OpenIDConnect的客户端与我们目前添加的OAuth2.0客户端非常相似。但由于OIDC中的流程始终是交互式的,因此我们需要在配置中添加一些重定向URL。
将以下内容添加到客户端配置中:
可以使用RequireConsent客户端对象上的属性基于每个客户端关闭同意。
在开发期间,您有时可能会看到一个异常,指出无法验证令牌。这是因为签名密钥材料是在运行中创建的,并且仅保留在内存中。当客户端和IdentityServer不同步时会发生此异常。只需在客户端重复操作,下次元数据赶上时,一切都应该再次正常工作。
最后一步是向MVC客户端添加注销。
确切的协议步骤在OpenIDConnect中间件中实现,只需将以下代码添加到某个控制器即可触发注销:
publicasyncTaskLogout(){awaitHttpContext.SignOutAsync("Cookies");awaitHttpContext.SignOutAsync("oidc");}这将清除本地cookie,然后重定向到IdentityServer。IdentityServer将清除其cookie,然后为用户提供返回MVC应用程序的链接。
接下来,我们将添加对外部认证的支持。这非常简单,因为您真正需要的是ASP.NETCore兼容的身份验证处理程序。
如果您在端口5000上运行-您只需使用下面代码段中的客户端ID/secret,因为这是我们预先注册的。
首先将Google身份验证处理程序添加到DI。这是通过添加该代码段完成ConfigureServices的Startup:
publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();//configureidentityserverwithin-memorystores,keys,clientsandscopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryIdentityResources(Config.GetIdentityResources()).AddInMemoryApiResources(Config.GetApiResources()).AddInMemoryClients(Config.GetClients()).AddTestUsers(Config.GetUsers());services.AddAuthentication().AddGoogle("Google",options=>{options.SignInScheme=IdentityServerConstants.ExternalCookieAuthenticationScheme;options.ClientId="434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com";options.ClientSecret="3gcoTrEDPPJ0ukn_aYYT6PWo";});}默认情况下,IdentityServer专门为外部身份验证的结果配置cookie处理程序(使用基于常量的方案IdentityServerConstants.ExternalCookieAuthenticationScheme)。然后,Google处理程序的配置使用该cookie处理程序。为了更好地理解如何完成此操作,请参阅Quickstart文件夹AccountController下的类。
将OpenIdConnect处理程序添加到DI:
在之前的快速入门中,我们探讨了API访问和用户身份验证。现在我们想把这两个部分放在一起。
OpenIDConnect和OAuth2.0组合的优点在于,您可以使用单个协议和使用令牌服务进行单次交换来实现这两者。
在之前的快速入门中,我们使用了OpenIDConnect隐式流程。在隐式流程中,所有令牌都通过浏览器传输,这对于身份令牌来说是完全正确的。现在我们还想要一个访问令牌。
访问令牌比身份令牌更敏感,如果不需要,我们不希望将它们暴露给“外部”世界。OpenIDConnect包含一个名为“混合流”的流程,它为我们提供了两全其美的优势,身份令牌通过浏览器渠道传输,因此客户端可以在进行任何更多工作之前对其进行验证。如果验证成功,客户端会打开令牌服务的反向通道以检索访问令牌。
没有太多必要的修改。首先,我们希望允许客户端使用混合流,此外我们还希望客户端允许执行不在用户上下文中的服务器到服务器API调用(这与我们的客户端凭证快速启动非常相似)。这是使用该AllowedGrantTypes属性表示的。
接下来我们需要添加一个客户端密钥。这将用于检索反向通道上的访问令牌。
最后,我们还让客户端访问offline_access范围-这允许请求刷新令牌以实现长期存在的API访问:
我们配置ClientSecret匹配IdentityServer的秘密。添加offline_access和api1范围,并设置ResponseType为(这基本上意味着“使用混合流”)codeid_token
OpenIDConnect中间件会自动为您保存令牌(在我们的案例中为身份,访问和刷新)。这就是SaveTokens设置的作用。
从技术上讲,令牌存储在cookie的属性部分中。访问它们的最简单方法是使用Microsoft.AspNetCore.Authentication命名空间中的扩展方法。
本快速入门假设您已经完成了所有之前的快速入门。本快速入门使用ASP.NET标识的方法是从VisualStudio中的ASP.NET标识模板创建一个新项目。这个新项目将取代我们在之前的快速入门中从头开始构建的先前IdentityServer项目。此解决方案中的所有其他项目(针对客户端和API)将保持不变。
首先创建一个新的“ASP.NET核心Web应用程序”项目。
然后选择“Web应用程序模板(模型-视图-控制器)”选项。
然后单击“更改身份验证”按钮,并选择“个人用户帐户”(这意味着使用ASP.NET身份):
最后,您的新项目对话框应该如下所示。完成后,单击“确定”以创建项目。
添加IdentityServer4.AspNetIdentityNuGet包。这取决于IdentityServer4包,因此会自动添加为传递依赖项。
必要的配置更改(暂时)是禁用MVC客户端的同意。我们还没有复制先前IdentityServer项目的同意代码,所以现在对MVC客户端进行一次修改并设置RequireConsent=false:
ConfigureServices
这显示了为ASP.NETIdentity生成的模板代码,以及IdentityServer所需的附加内容(最后)。在之前的快速入门中,AddTestUsers扩展方法用于注册用户,但在这种情况下,我们将该扩展方法替换AddAspNetIdentity为使用ASP.NETIdentity用户。该AddAspNetIdentity扩展方法需要一个通用的参数,它是你的ASP.NET身份用户类型(同一个在需要AddIdentity从模板方法)。
publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContext
在使用ASP.NET标识时,在DI系统中在ASP.NET标识之后注册IdentityServer非常重要,因为IdentityServer会从ASP.NET标识覆盖某些配置。
配置
这显示了为ASP.NETIdentity生成的模板代码,以及UseIdentityServer替换调用的调用UseAuthentication。
publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseBrowserLink();app.UseDatabaseErrorPage();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();//app.UseAuthentication();//notneeded,sinceUseIdentityServeraddstheauthenticationmiddlewareapp.UseIdentityServer();app.UseMvc(routes=>{routes.MapRoute(name:"default",template:"{controller=Home}/{action=Index}/{id}");});}创建用户数据库鉴于这是一个新的ASP.NETIdentity项目,您将需要创建数据库。您可以通过从项目目录运行命令提示符并运行来执行此操作,如下所示:dotnetefdatabaseupdate-cApplicationDbContext
此时,您应该能够运行项目并在数据库中创建/注册用户。启动应用程序,然后从主页单击“注册”链接:
在注册页面上创建一个新的用户帐户:
您还应该能够单击“使用应用程序标识调用API”来代表用户调用API:
IdentityServer的先前快速入门项目提供了同意页面,错误页面和注销页面。这些缺失部分的代码可以简单地从之前的快速入门项目复制到此项目中。完成后,您最终可以删除/删除旧的IdentityServer项目。此外,一旦完成此操作,请不要忘记RequireConsent=true在MVC客户端配置上重新启用该标志。
为JavaScript应用程序创建一个新项目。它可以只是一个空的Web项目,也可以是一个空的ASP.NETCore应用程序。此快速入门将使用空的ASP.NETCore应用程序。
创建一个新的ASP.NETCoreWeb应用程序:
选择“空”模板:
单击“确定”按钮以创建项目。
鉴于该项目主要用于客户端,我们需要ASP.NETCore来提供构成我们应用程序的静态HTML和JavaScript文件。静态文件中间件旨在实现此目的。
在方法中注册Startup.cs中的静态文件中间件Configure:
publicvoidConfigure(IApplicationBuilderapp){app.UseDefaultFiles();app.UseStaticFiles();}此中间件现在将从应用程序的/wwwroot文件夹中提供静态文件。这是我们将放置HTML和JavaScript文件的地方。
NPM
如果要使用NPM下载oidc-client,请按照以下步骤操作:
将新的NPM包文件添加到项目中并将其命名为package.json:
在的package.json一个补充dependency到oidc-client:
"dependencies":{"oidc-client":"1.4.1"}保存此文件后,VisualStudio应自动将这些包还原到名为node_modules的文件夹中:
在/node_modules/oidc-client/dist文件夹中找到名为oidc-client.js的文件,并将其复制到应用程序的/wwwroot文件夹中。有更复杂的方法将NPM包复制到/wwwroot,但这些技术超出了本快速入门的范围。
接下来是将您的HTML和JavaScript文件添加到/wwwroot。我们将有两个HTML文件和一个特定于应用程序的JavaScript文件(除了oidc-client.js库)。在/wwwroot中,添加一个名为index.html和callback.html的HTML文件,并添加一个名为app.js的JavaScript文件。
的index.html
它应该如下所示:
这将包含我们的应用程序的主要代码。第一件事是添加一个帮助函数来将消息记录到
:functionlog(){document.getElementById('results').innerText='';Array.prototype.forEach.call(arguments,function(msg){if(msginstanceofError){msg="Error:"+msg.message;}elseif(typeofmsg!=='string'){msg=JSON.stringify(msg,null,2);}document.getElementById('results').innerHTML+=msg+'\r\n';});}接下来,添加代码以将“click”事件处理程序注册到三个按钮:
document.getElementById("login").addEventListener("click",login,false);document.getElementById("api").addEventListener("click",api,false);document.getElementById("logout").addEventListener("click",logout,false);接下来,我们可以使用UserManager类的OIDC客户端库来管理ID连接协议。它需要MVCClient中必需的类似配置(尽管具有不同的值)。添加此代码以配置和实例化UserManager:
客户注册加入IdentityServer的JavaScript客户端既然客户端应用程序已经准备就绪,我们需要在IdentityServer中为这个新的JavaScript客户端定义一个配置条目。在IdentityServer项目中找到客户端配置(在Config.cs中)。将新客户端添加到我们的新JavaScript应用程序的列表中。它应具有下面列出的配置: 配置CORS
ConfigureServices在Startup.cs中将CORS服务添加到依赖注入系统:
publicvoidConfigure(IApplicationBuilderapp){app.UseCors("default");app.UseAuthentication();app.UseMvc();}运行JavaScript应用程序现在您应该能够运行JavaScript客户端应用程序:
然后单击“API”按钮以调用WebAPI:
最后点击“退出”以签署用户。
IdentityServer旨在实现可扩展性,其中一个可扩展点是用于IdentityServer所需数据的存储机制。本快速入门展示了如何配置IdentityServer以使用EntityFramework(EF)作为此数据的存储机制(而不是使用我们迄今为止使用的内存中实现)。
我们正在向数据库移动两种类型的数据。第一个是配置数据(资源和客户端)。第二个是IdentityServer在使用时产生的操作数据(令牌,代码和同意)。这些存储使用接口建模,我们在IdentityServer4.EntityFrameworkNuget包中提供这些接口的EF实现。
通过添加IdentityServer项目的IdentityServer4.EntityFrameworkNuget包的引用开始。
鉴于EF的灵活性,您可以使用任何EF支持的数据库。对于本快速入门,我们将使用VisualStudio附带的SqlServer的LocalDb版本。
根据您为IdentityServer主机创建初始项目的方式,您可能已在csproj文件中配置了这些工具。如果是,您可以跳到下一部分。
然后在结尾元素之前添加以下代码段:
它应该看起来像这样: 保存并关闭文件。要测试您是否正确安装了这些工具,可以在与项目相同的目录中打开命令shell并运行dotnetef。它应该如下所示:
接下来的步骤是,以取代当前呼叫AddInMemoryClients,AddInMemoryIdentityResources和AddInMemoryApiResources在ConfigureServices在方法Startup.cs。我们将使用以下代码替换它们:
conststringconnectionString=@"DataSource=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;";varmigrationsAssembly=typeof(Startup).GetTypeInfo().Assembly.GetName().Name;//configureidentityserverwithin-memorystores,keys,clientsandscopesservices.AddIdentityServer().AddDeveloperSigningCredential().AddTestUsers(Config.GetUsers())//thisaddstheconfigdatafromDB(clients,resources).AddConfigurationStore(options=>{options.ConfigureDbContext=builder=>builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly));})//thisaddstheoperationaldatafromDB(codes,tokens,consents).AddOperationalStore(options=>{options.ConfigureDbContext=builder=>builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly));//thisenablesautomatictokencleanup.thisisoptional.options.EnableTokenCleanup=true;options.TokenCleanupInterval=30;});您可能需要将这些命名空间添加到文件中:
usingMicrosoft.EntityFrameworkCore;usingSystem.Reflection;上面的代码是对连接字符串进行硬编码,如果您愿意,可以随意更改。此外,调用AddConfigurationStore和AddOperationalStore注册EF支持的商店实现。
传递给这些API的“构建器”回调函数是EF机制,允许您为这两个存储中的每一个配置DbContextOptionsBuilderforDbContext。这就是我们的DbContext类可以使用您要使用的数据库提供程序进行配置的方式。在这种情况下,通过调用UseSqlServer我们正在使用SqlServer。您也可以看出,这是提供连接字符串的位置。
“options”回调函数用于UseSqlServer配置定义EF迁移的程序集。EF需要使用迁移来定义数据库的模式。
托管应用程序负责定义这些迁移,因为它们特定于您的数据库和提供程序。
我们接下来会添加迁移。
要创建迁移,请在IdentityServer项目目录中打开命令提示符。在命令提示符下运行以下两个命令:
dotnetefmigrationsaddInitialIdentityServerPersistedGrantDbMigration-cPersistedGrantDbContext-oData/Migrations/IdentityServer/PersistedGrantDbdotnetefmigrationsaddInitialIdentityServerConfigurationDbMigration-cConfigurationDbContext-oData/Migrations/IdentityServer/ConfigurationDb它应该看起来像这样:
您现在应该在项目中看到/Data/Migrations/IdentityServer文件夹。其中包含新创建的迁移的代码。
现在我们已经进行了迁移,我们可以编写代码来从迁移中创建数据库。我们还将使用我们在之前的快速入门中定义的内存配置数据来为数据库设定种子。
在Startup.cs中添加此方法以帮助初始化数据库:
privatevoidInitializeDatabase(IApplicationBuilderapp){using(varserviceScope=app.ApplicationServices.GetService
().CreateScope()){serviceScope.ServiceProvider.GetRequiredService ().Database.Migrate();varcontext=serviceScope.ServiceProvider.GetRequiredService ();context.Database.Migrate();if(!context.Clients.Any()){foreach(varclientinConfig.GetClients()){context.Clients.Add(client.ToEntity());}context.SaveChanges();}if(!context.IdentityResources.Any()){foreach(varresourceinConfig.GetIdentityResources()){context.IdentityResources.Add(resource.ToEntity());}context.SaveChanges();}if(!context.ApiResources.Any()){foreach(varresourceinConfig.GetApiResources()){context.ApiResources.Add(resource.ToEntity());}context.SaveChanges();}}}然后我们可以从Configure方法中调用它: publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory){//thiswilldotheinitialDBpopulationInitializeDatabase(app);//therestofthecodethatwasalreadyhere//...}现在,如果运行IdentityServer项目,则应创建数据库并使用快速入门配置数据进行种子设定。您应该能够使用SQLServerManagementStudio或VisualStudio来连接和检查数据。
上面的InitializeDatabase辅助API可以方便地为数据库设定种子,但是这种方法并不适合每次运行应用程序时执行。填充数据库后,请考虑删除对API的调用。
本节中的代码仍然依赖于Config.cs及其虚构用户Alice和Bob。如果您的用户列表很简短且静态,则调整后的Config.cs版本可能就足够了,但您可能希望在数据库中动态管理更大且更流畅的用户列表。ASP.NETIdentity是一个需要考虑的选项,下一节的快速入门列出了此解决方案的示例实现。
IdentityServer组织不维护这些示例。IdentityServer组织愉快地链接到社区样本,但不能对样本做出任何保证。请直接与作者联系。
此示例显示如何在与保护API的IdentityServer相同的主机中托管API。
IdentityServer是中间件和服务的组合。所有配置都在您的启动类中完成。
您可以通过调用以下方法将IdentityServer服务添加到DI系统:
这将返回一个构建器对象,该构建器对象又有许多方便的方法来连接其他服务。
各种“内存中”配置API允许从内存中的配置对象列表配置IdentityServer。这些“内存中”集合可以在宿主应用程序中进行硬编码,也可以从配置文件或数据库动态加载。但是,通过设计,这些集合仅在托管应用程序启动时创建。
使用这些配置API的目的是在原型设计,开发和/或测试时使用,在这种情况下,不需要在运行时为配置数据动态查询数据库。如果配置很少更改,则此配置样式也可能适用于生产方案,或者如果必须更改值,则要求重新启动应用程序并不方便。
该TestUser级车型的用户,他们的凭据,并在IdentityServer索赔。使用TestUser“内存”商店是因为它适用于原型设计,开发和/或测试。采用TestUser在生产中不推荐使用。
IdentityServer经常使用客户端和资源配置数据。如果从数据库或其他外部存储加载此数据,则频繁重新加载相同数据可能会很昂贵。
可以进一步自定义缓存:
默认缓存依赖于ICache
实现。如果要自定义特定配置对象的缓存行为,可以在依赖项注入系统中替换此实现。 ICache
本身的默认实现依赖于.NET提供的IMemoryCache接口(和MemoryCache实现)。如果要自定义内存中缓存行为,可以替换IMemoryCache依赖项注入系统中的实现。 您需要通过调用以下方法将IdentityServer添加到管道:
publicvoidConfigure(IApplicationBuilderapp){app.UseIdentityServer();}注意
中间件没有其他配置。
您通常在系统中定义的第一件事是您要保护的资源。这可能是您的用户的身份信息,如个人资料数据或电子邮件地址,或访问API。
您可以使用C#对象模型定义资源-或从数据存储加载它们。的实施IResourceStore与这些低级别的细节交易。对于本文档,我们使用内存中实现。
要允许客户端请求API的访问令牌,您需要定义API资源,例如:
要获取API的访问权限,您还需要将它们注册为范围。这次范围类型是Resource类型:
客户端表示可以从您的身份服务器请求令牌的应用程序。
详细信息各不相同,但您通常会为客户端定义以下常用设置:
在运行时,通过实现来检索客户端IClientStore。这允许从任意数据源(如配置文件或数据库)加载它们。对于本文档,我们将使用客户端存储的内存版本。您可以ConfigureServices通过AddInMemoryClientsextensions方法连接内存存储。
在这种情况下,没有交互式用户-服务(也称为客户端)想要与API(aka范围)进行通信:
publicclassClients{publicstaticIEnumerable
Get(){returnnewList {newClient{ClientId="service.client",ClientSecrets={newSecret("secret".Sha256())},AllowedGrantTypes=GrantTypes.ClientCredentials,AllowedScopes={"api1","api2.read_only"}}};}}定义基于浏览器的JavaScript客户端(例如SPA)以进行用户身份验证和委派访问以及此客户端使用所谓的隐式流来从JavaScript请求身份和访问令牌: 交互式服务器端(或本机桌面/移动)应用程序使用混合流。此流程为您提供最佳安全性,因为访问令牌仅通过反向通道调用传输(并允许您访问刷新令牌):
IdentityServer注册了两个cookie处理程序(一个用于身份验证会话,另一个用于临时外部cookie)。默认情况下使用它们,如果要手动引用它们,可以从IdentityServerConstants类(DefaultCookieAuthenticationScheme和ExternalCookieAuthenticationScheme)中获取它们的名称。
我们只公开这些cookie的基本设置(到期和滑动),如果您需要更多控制,您可以注册自己的cookie处理程序。IdentityServer使用与使用ASP.NETCore时DefaultAuthenticateScheme配置的cookie处理程序相匹配的cookie处理程序。AuthenticationOptions``AddAuthentication
如果您希望使用自己的cookie身份验证处理程序,则必须自己配置它。这必须ConfigureServices在DI(withAddIdentityServer)中注册IdentityServer之后完成。例如:
services.AddIdentityServer().AddInMemoryClients(Clients.Get()).AddInMemoryIdentityResources(Resources.GetIdentityResources()).AddInMemoryApiResources(Resources.GetApiResources()).AddDeveloperSigningCredential().AddTestUsers(TestUsers.Users);services.AddAuthentication("MyCookie").AddCookie("MyCookie",options=>{options.ExpireTimeSpan=...;});注意
IdentityServer在内部调用两个AddAuthentication并AddCookie使用自定义方案(通过常量IdentityServerConstants.DefaultCookieAuthenticationScheme),因此要覆盖它们,您必须在之后进行相同的调用AddIdentityServer。
IdentityServer不为用户身份验证提供任何用户界面或用户数据库。这些是您希望自己提供或发展的东西。
当您签署用户时,您必须至少发出sub索赔和name索赔。IdentityServer还提供了一些SignInAsync扩展方法HttpContext,使其更加方便。
ASP.NETCore有一种灵活的方式来处理外部身份验证。这涉及几个步骤。
与外部提供者通信所需的协议实现封装在身份验证处理程序中。一些提供商使用专有协议(例如Facebook等社交提供商),有些提供商使用标准协议,例如OpenIDConnect,WS-Federation或SAML2p。
调用外部身份验证处理程序的一个选项SignInScheme,例如:
鉴于这是一种常见做法,IdentityServer专门为此外部提供程序工作流注册cookie处理程序。该方案通过IdentityServerConstants.ExternalCookieAuthenticationScheme常数表示。如果您要使用我们的外部cookie处理程序,那么对于SignInScheme上面的内容,您将赋值为IdentityServerConstants.ExternalCookieAuthenticationScheme常量:
services.AddAuthentication().AddGoogle("Google",options=>{options.SignInScheme=IdentityServerConstants.ExternalCookieAuthenticationScheme;options.ClientId="...";options.ClientSecret="...";})您也可以注册自己的自定义cookie处理程序,如下所示:
services.AddAuthentication().AddCookie("YourCustomScheme").AddGoogle("Google",options=>{options.SignInScheme="YourCustomScheme";options.ClientId="...";options.ClientSecret="...";})注意
您可以通过(或使用MVC)ChallengeAsync上的扩展方法调用外部认证处理程序。HttpContext``ChallengeResult
您通常希望将一些选项传递给挑战操作,例如回调页面的路径和簿记提供者的名称,例如:
varcallbackUrl=Url.Action("ExternalLoginCallback");varprops=newAuthenticationProperties{RedirectUri=callbackUrl,Items={{"scheme",provider},{"returnUrl",returnUrl}}};returnChallenge(provider,props);处理回调并签署用户在回调页面上,您的典型任务是:
检查外部身份:
幸运的是,IdentityServer为您提供了一个实现,由IDistributedCacheDI容器中注册的实现(例如标准MemoryDistributedCache)支持。要使用IdentityServer提供的安全数据格式实现,只需在配置DI时调用AddOidcStateDataFormatterCache扩展方法IServiceCollection。如果未传递任何参数,则配置的所有OpenIDConnect处理程序将使用IdentityServer提供的安全数据格式实现:
publicvoidConfigureServices(IServiceCollectionservices){//configurestheOpenIdConnecthandlerstopersistthestateparameterintotheserver-sideIDistributedCache.services.AddOidcStateDataFormatterCache();services.AddAuthentication().AddOpenIdConnect("demoidsrv","IdentityServer",options=>{//...}).AddOpenIdConnect("aad","AzureAD",options=>{//...}).AddOpenIdConnect("adfs","ADFS",options=>{//...});}如果只配置特定方案,则将这些方案作为参数传递:
publicvoidConfigureServices(IServiceCollectionservices){//configurestheOpenIdConnecthandlerstopersistthestateparameterintotheserver-sideIDistributedCache.services.AddOidcStateDataFormatterCache("aad","demoidsrv");services.AddAuthentication().AddOpenIdConnect("demoidsrv","IdentityServer",options=>{//...}).AddOpenIdConnect("aad","AzureAD",options=>{//...}).AddOpenIdConnect("adfs","ADFS",options=>{//...});}Windows身份验证在支持的平台上,您可以使用IdentityServer对使用Windows身份验证的用户进行身份验证(例如,针对ActiveDirectory)。当前使用以下命令托管IdentityServer时,Windows身份验证可用:
使用Kestrel时,必须运行“后面”IIS并使用IIS集成:
IIS(或IISExpress)中的虚拟目录也必须启用Windows并启用匿名身份验证。
IIS集成层将Windows身份验证处理程序配置为DI,可以通过身份验证服务调用。通常在IdentityServer中,建议禁用此自动行为。这是在ConfigureServices:
services.Configure
(iis=>{iis.AuthenticationDisplayName="Windows";iis.AutomaticAuthentication=false;});注意 默认情况下,显示名称为空,Windows身份验证按钮不会显示在快速入门UI中。如果依赖于自动发现外部提供程序,则需要设置显示名称。
注销IdentityServer就像删除身份验证cookie一样简单,但是为了完成联合注销,我们必须考虑将用户从客户端应用程序(甚至可能是上游身份提供商)中签名。
要删除身份验证cookie,只需使用SignOutAsync扩展方法即可HttpContext。您将需要传递使用的方案(IdentityServerConstants.DefaultCookieAuthenticationScheme除非您已更改,否则提供此方案):
awaitHttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);或者您可以使用IdentityServer提供的便捷扩展方法:
awaitHttpContext.SignOutAsync();注意
通常,您应该提示用户注销(意味着需要POST),否则攻击者可能会链接到您的注销页面,导致用户自动注销。
前端服务器端客户端
反向通道服务器端客户端
要通过反向通道规范从服务器端客户端应用程序注销用户SignOutIFrameUrl,IdentityServer中的端点将自动触发服务器到服务器调用,将签名的注销请求传递给客户端。这意味着即使如果没有前面通道的客户端中,“退出”,在IdentityServer页仍必须渲染
基于浏览器的JavaScript客户端
默认情况下,此状态作为通过logoutId值传递的受保护数据结构进行管理。如果您希望在结束会话端点和注销页面之间使用其他一些持久性,那么您可以IMessageStore
在DI中实现并注册实现。 由于正常注销工作流程已经需要清理和状态管理,因此将用户重定向到外部身份提供商是有问题的。然后,在IdentityServer完成正常注销和清理过程的唯一方法是从外部身份提供程序请求在注销后将用户重定向回IdentityServer。并非所有外部提供商都支持退出后重定向,因为它取决于它们支持的协议和功能。
所有这些因素的净效果是没有像正常注销工作流那样呈现“注销”页面,这意味着我们缺少对IdentityServer客户端的注销通知。我们必须为每个联合注销端点添加代码,以呈现必要的通知以实现联合注销。
通用架构是所谓的联合网关。在这种方法中,IdentityServer充当一个或多个外部身份提供者的网关。
该架构具有以下优点
换句话说-拥有联合网关可以让您对身份基础架构进行大量控制。由于您的用户身份是您最重要的资产之一,我们建议您控制网关。
同意页面通常呈现当前用户的显示名称,请求访问的客户端的显示名称,客户端的徽标,有关客户端的更多信息的链接以及客户端请求访问的资源列表。允许用户表明他们的同意应该被“记住”也是很常见的,因此将来不会再次提示同一客户。
可以使用IClientStore和IResourceStore接口获取有关客户端或资源的其他详细信息。
保护基于ASP.NET核心的API只需在DI中配置JWT承载认证处理程序,并将认证中间件添加到管道:
对于最简单的情况,我们的处理程序配置看起来非常类似于上面的代码段:
如果传入令牌不是JWT,我们的中间件将联系发现文档中的内省端点以验证令牌。由于内省端点需要身份验证,因此您需要提供已配置的API密钥,例如:
所述ApiName属性检查该令牌具有匹配观众(或短aud),如权利要求。
制定全球政策:
services.AddMvcCore(options=>{//requirescope1orscope2varpolicy=ScopePolicy.Create("scope1","scope2");options.Filters.Add(newAuthorizeFilter(policy));}).AddJsonFormatters().AddAuthorization();制定范围政策:
通常,您将设计IdentityServer部署以实现高可用性:
IdentityServer本身是无状态的,不需要服务器关联-但是有些数据需要在实例之间共享。
这通常包括:
存储数据的方式取决于您的环境。在配置数据很少更改的情况下,我们建议使用内存存储和代码或配置文件。
在高度动态的环境(例如Saas)中,我们建议使用数据库或配置服务动态加载配置。
您还可以通过实现IResourceStore和构建自己的配置存储IClientStore。
对于某些操作,IdentityServer需要持久性存储来保持状态,这包括:
您可以使用传统数据库存储操作数据,也可以使用具有Redis等持久性功能的缓存。上面提到的EFCore实现也支持运营数据。
您还可以通过实现实现对自己的自定义存储机制的支持IPersistedGrantStore-默认情况下IdentityServer会注入内存中的版本。
您可以重复使用上述持久性存储之一,也可以使用像共享文件这样的简单文件。
我们大致遵循Microsoft使用日志级别的指导原则:
对于以下配置,您需要Serilog.AspNetCore和Serilog.Sinks.Console包:
publicclassProgram{publicstaticvoidMain(string[]args){Console.Title="IdentityServer4";Log.Logger=newLoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft",LogEventLevel.Warning).MinimumLevel.Override("System",LogEventLevel.Warning).MinimumLevel.Override("Microsoft.AspNetCore.Authentication",LogEventLevel.Information).Enrich.FromLogContext().WriteTo.Console(outputTemplate:"[{Timestamp:HH:mm:ss}{Level}]{SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",theme:AnsiConsoleTheme.Literate).CreateLogger();BuildWebHost(args).Run();}publicstaticIWebHostBuildWebHost(string[]args){returnWebHost.CreateDefaultBuilder(args).UseStartup
().UseSerilog().Build();}}.NET对于以下配置,您需要Serilog.Extensions.Logging和Serilog.Sinks.Console包: publicclassProgram{publicstaticvoidMain(string[]args){Console.Title="IdentityServer4";Log.Logger=newLoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft",LogEventLevel.Warning).MinimumLevel.Override("System",LogEventLevel.Warning).MinimumLevel.Override("Microsoft.AspNetCore.Authentication",LogEventLevel.Information).Enrich.FromLogContext().WriteTo.Console(outputTemplate:"[{Timestamp:HH:mm:ss}{Level}]{SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",theme:AnsiConsoleTheme.Literate).CreateLogger();BuildWebHost(args).Run();}publicstaticIWebHostBuildWebHost(string[]args){returnWebHost.CreateDefaultBuilder(args).UseStartup
().ConfigureLogging(builder=>{builder.ClearProviders();builder.AddSerilog();}).Build();}}事件日志记录是更低级别的“printf”样式-事件代表有关IdentityServer中某些操作的更高级别信息。事件是结构化数据,包括事件ID,成功/失败信息,类别和详细信息。这使得查询和分析它们变得容易,并提取可用于进一步处理的有用信息。 默认情况下不会启用事件-但可以在ConfigureServices方法中进行全局配置,例如:
services.AddIdentityServer(options=>{options.Events.RaiseSuccessEvents=true;options.Events.RaiseFailureEvents=true;options.Events.RaiseErrorEvents=true;});要发出事件,请使用IEventServiceDI容器并调用RaiseAsync方法,例如:
publicasyncTask
Login(LoginInputModelmodel){if(_users.ValidateCredentials(model.Username,model.Password)){//issueauthenticationcookiewithsubjectIDandusernamevaruser=_users.FindByUsername(model.Username);await_events.RaiseAsync(newUserLoginSuccessEvent(user.Username,user.SubjectId,user.Username));}else{await_events.RaiseAsync(newUserLoginFailureEvent(model.Username,"invalidcredentials"));}}自定义接收器我们的默认事件接收器只是将事件类序列化为JSON并将其转发到ASP.NETCore日志系统。如果要连接到自定义事件存储,请实现该IEventSink接口并将其注册到DI。 IdentityServer中定义了以下事件:
您可以创建自己的事件并通过我们的基础架构发出它们。
publicclassUserLoginFailureEvent:Event{publicUserLoginFailureEvent(stringusername,stringerror):base(EventCategories.Authentication,"UserLoginFailure",EventTypes.Failure,EventIds.UserLoginFailure,error){Username=username;}publicstringUsername{get;set;}}密码学,密钥和IdentityServer依赖于几个加密机制来完成其工作。
IdentityServer需要非对称密钥对来签署和验证JWT。此密钥对可以是证书/私钥组合或原始RSA密钥。无论如何,它必须支持带有SHA256的RSA。
加载签名密钥和相应的验证部分是通过ISigningCredentialStore和的实现来完成的IValidationKeysStore。如果要自定义加载密钥,可以实现这些接口并将其注册到DI。
虽然一次只能使用一个签名密钥,但您可以将多个验证密钥发布到发现文档。这对于密钥翻转很有用。
翻转通常如下所示:
这要求客户端和API使用发现文档,并且还具有定期刷新其配置的功能。
我们不强制使用HTTPS,但对于生产,它必须与IdentityServer进行每次交互。
Client.AllowedGrantTypes={GrantType.Hybrid,GrantType.ClientCredentials,"my_custom_grant_type"};如果要通过浏览器通道传输访问令牌,还需要在客户端配置上明确允许:
Client.AllowAccessTokensViaBrowser=true;注意
资源所有者密码授予类型允许通过将用户的名称和密码发送到令牌端点来代表用户请求令牌。这就是所谓的“非交互式”身份验证,通常不推荐使用。
在隐式流程中,所有令牌都通过浏览器传输,因此不允许使用刷新令牌等高级功能。
这是希望检索访问令牌(也可能是刷新令牌)的本机应用程序的推荐流程,用于服务器端Web应用程序和本机桌面/移动应用程序。
刷新令牌允许获得对API的长期访问。
您通常希望尽可能缩短访问令牌的生命周期,但同时不希望通过对IdentityServer进行前端通道往返来请求新的令牌一次又一次地打扰用户。
刷新令牌允许在没有用户交互的情况下请求新的访问令牌。每次客户端刷新令牌时,都需要对IdentityServer进行(经过身份验证的)反向通道调用。这允许检查刷新令牌是否仍然有效,或者在此期间是否已被撤销。
在某些情况下,客户端需要使用身份服务器进行身份验证,例如
为此,您可以将秘密列表分配给客户端或API资源。
秘密解析和验证是身份服务器中的可扩展点,开箱即用它支持共享机密以及通过基本身份验证头或POST主体传输共享机密。
以下代码设置散列共享密钥:
varsecret=newSecret("secret".Sha256());现在可以将此秘密分配给aClient或anApiResource。请注意,它们不仅支持单个秘密,还支持多个秘密。这对于秘密翻转和轮换非常有用:
varclient=newClient{ClientId="client",ClientSecrets=newList
{secret},AllowedGrantTypes=GrantTypes.ClientCredentials,AllowedScopes=newList {"api1","api2"}};实际上,您还可以为秘密分配说明和到期日期。该描述将用于记录,以及强制执行秘密生存期的到期日期: varsecret=newSecret("secret".Sha256(),"2016secret",newDateTime(2016,12,31));使用共享密钥进行身份验证您可以将客户端ID/机密组合作为POST正文的一部分发送:
POST/connect/tokenclient_id=client1&client_secret=secret&.....或作为基本身份验证标头:
POST/connect/tokenAuthorization:Basicxxxxx...您可以使用以下C#代码手动创建基本身份验证标头:
秘密可扩展性通常包含三件事:
秘密解析器和验证器是ISecretParser和ISecretValidator接口的实现。要使它们可用于IdentityServer,您需要将它们注册到DI容器,例如:
builder.AddSecretParser
()builder.AddSecretValidator ()我们的默认私钥JWT秘密验证器期望完整(叶)证书作为秘密定义的base64。然后,此证书将用于验证自签名JWT上的签名,例如: publicinterfaceIExtensionGrantValidator{///
///Handlesthecustomgrantrequest./// ///Thevalidationcontext.TaskValidateAsync(ExtensionGrantValidationContextcontext);/// ///Returnsthegranttypethisvalidatorcandealwith/// //////Thetypeofthegrant./// stringGrantType{get;}}该ExtensionGrantValidationContext对象使您可以访问:换句话说,中间层API(API1)需要包含用户身份的访问令牌,但需要具有后端API(API2)的范围。
前端会将令牌发送到API1,现在需要在IdentityServer上使用API2的新令牌交换此令牌。
在线上,对交换的令牌服务的调用可能如下所示:
publicclassDelegationGrantValidator:IExtensionGrantValidator{privatereadonlyITokenValidator_validator;publicDelegationGrantValidator(ITokenValidatorvalidator){_validator=validator;}publicstringGrantType=>"delegation";publicasyncTaskValidateAsync(ExtensionGrantValidationContextcontext){varuserToken=context.Request.Raw.Get("token");if(string.IsNullOrEmpty(userToken)){context.Result=newGrantValidationResult(TokenRequestErrors.InvalidGrant);return;}varresult=await_validator.ValidateAccessTokenAsync(userToken);if(result.IsError){context.Result=newGrantValidationResult(TokenRequestErrors.InvalidGrant);return;}//getuser'sidentityvarsub=result.Claims.FirstOrDefault(c=>c.Type=="sub").Value;context.Result=newGrantValidationResult(sub,GrantType);return;}}不要忘记在DI上注册验证器。
注册委托客户端
varclient=newclient{ClientId="api1.client",ClientSecrets=newList
{newSecret("secret".Sha256())},AllowedGrantTypes={"delegation"},AllowedScopes=newList {"api2"}}调用令牌端点 在API1中,您现在可以自己构建HTTP有效内容,或使用IdentityModel帮助程序库:
publicasyncTask
DelegateAsync(stringuserToken){varpayload=new{token=userToken};//createtokenclientvarclient=newTokenClient(disco.TokenEndpoint,"api1.client","secret");//sendcustomgranttotokenendpoint,returnresponsereturnawaitclient.RequestCustomGrantAsync("delegation","api2",payload);}现在TokenResponse.AccessToken将包含委托访问令牌。 publicinterfaceIResourceOwnerPasswordValidator{///
///Validatestheresourceownerpasswordcredential/// ///Thecontext.TaskValidateAsync(ResourceOwnerPasswordValidationContextcontext);}在上下文中,您将找到已解析的协议参数,如UserName和Password,以及原始请求,如果您想查看其他输入数据。 由于访问令牌的生命周期有限,因此刷新令牌允许在没有用户交互的情况下请求新的访问令牌。
访问令牌可以有两种形式-自包含或参考。
使用引用令牌时-IdentityServer会将令牌的内容存储在数据存储中,并且只会将此令牌的唯一标识符发回给客户端。接收此引用的API必须打开与IdentityServer的反向通道通信以验证令牌。
您可以使用以下设置切换客户端的令牌类型:
内省端点需要身份验证-因为内省端点的客户端是API,您可以在以下位置配置秘密ApiResource:
如果您使用我们提供的“内存中”或基于EF的客户端配置,则将使用此默认CORS实现。如果您定义自己的IClientStore,那么您将需要实现自己的自定义CORS策略服务(见下文)。
IdentityServer允许托管应用程序实现ICorsPolicyService完全控制CORS策略。
要实现的单一方法是:。如果允许原点则返回,否则返回。Task
IsOriginAllowedAsync(stringorigin)``true``false 实现后,只需在DI中注册实现,然后IdentityServer将使用您的自定义实现。
DefaultCorsPolicyService
如果您只是希望硬编码一组允许的原点,那么ICorsPolicyService可以使用一个预先构建的实现调用DefaultCorsPolicyService。这将被配置为DI单身,并以其硬编码的AllowedOrigins收集,或设置标志AllowAll,以true允许所有的起源。例如,在ConfigureServices:
AllowAll谨慎使用。
IdentityServer使用ASP.NETCore的CORS中间件来提供其CORS实现。托管IdentityServer的应用程序可能还需要CORS用于自己的自定义端点。通常,两者应该在同一个应用程序中一起工作。
您的代码应使用ASP.NETCore中记录的CORS功能,而不考虑IdentityServer。这意味着您应该定义策略并正常注册中间件。如果您的应用程序定义了策略ConfigureServices,那么这些策略应该继续在您使用它们的相同位置(在您配置CORS中间件的位置或在EnableCors控制器代码中使用MVC属性的位置)。相反,如果您使用CORS中间件(通过策略构建器回调)定义内联策略,那么它也应该继续正常工作。
在您使用ASP.NETCoreCORS服务和IdentityServer之间可能存在冲突的一种情况是,如果您决定创建自定义ICorsPolicyProvider。鉴于ASP.NETCore的CORS服务和中间件的设计,IdentityServer实现了自己的自定义ICorsPolicyProvider并将其注册到DI系统中。幸运的是,IdentityServer实现旨在使用装饰器模式来包装ICorsPolicyProvider已在DI中注册的任何现有模式。这意味着你也可以实现ICorsPolicyProvider,但它只需要在DI中的IdentityServer之前注册(例如,在ConfigureServices)。
默认情况下,所有信息都包含在发现文档中,但通过使用配置选项,您可以隐藏各个部分,例如:
services.AddIdentityServer(options=>{options.Discovery.ShowIdentityScopes=false;options.Discovery.ShowApiScopes=false;options.Discovery.ShowClaims=false;options.Discovery.ShowExtensionGrantTypes=false;});扩展发现您可以向发现文档添加自定义条目,例如:
services.AddIdentityServer(options=>{options.Discovery.CustomEntries.Add("my_setting","foo");options.Discovery.CustomEntries.Add("my_complex_setting",new{foo="foo",bar="bar"});});当您添加以开头的自定义值时,它将扩展到IdentityServer基址以下的绝对路径,例如:
options.Discovery.CustomEntries.Add("my_custom_endpoint","~/custom");如果要完全控制发现(和jwks)文档的呈现,可以实现IDiscoveryResponseGenerator接口(或从我们的默认实现派生)。
您可以向托管IdentityServer4的应用程序添加更多API端点。
publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();//detailsomittedservices.AddIdentityServer();services.AddAuthentication().AddIdentityServerAuthentication("token",isAuth=>{isAuth.Authority="base_address_of_identityserver";isAuth.ApiName="name_of_api";});}在您的API上,您需要添加[Authorize]属性并显式引用您要使用的身份验证方案(token在此示例中,您可以选择您喜欢的任何名称):
如果需要,您还可以将端点添加到发现文档中,例如:
services.AddIdentityServer(options=>{options.Discovery.CustomEntries.Add("custom_endpoint","~/api/custom");})添加新协议除了对OpenIDConnect和OAuth2.0的内置支持之外,IdentityServer4还允许添加对其他协议的支持。
您可以将这些附加协议端点添加为中间件或使用例如MVC控制器。在这两种情况下,您都可以访问ASP.NETCoreDI系统,该系统允许重用我们的内部服务,例如访问客户端定义或密钥材料。
身份验证请求通常如下所示:
要实现上述工作流程,需要与IdentityServer建立一些交互点。
所述IIdentityServerInteractionService支撑件转动一个协议返回URL成解析和验证上下文对象:
varcontext=await_interaction.GetAuthorizationContextAsync(returnUrl);默认情况下,交互服务仅了解OpenIDConnect协议消息。要扩展支持,您可以自己编写IReturnUrlParser:
publicinterfaceIReturnUrlParser{boolIsValidReturnUrl(stringreturnUrl);Task
ParseAsync(stringreturnUrl);}..然后在DI中注册解析器: 访问用于创建协议响应的配置和密钥材料
通过将IKeyMaterialService代码注入到代码中,您可以访问配置的签名凭据和验证密钥:
发现端点可通过/.well-known/openid-configuration相对于基地址获得,例如:
例
GET/connect/userinfoAuthorization:Bearer
HTTP/1.1200OKContent-Type:application/json{"sub":"248289761001","name":"BobSmith","given_name":"Bob","family_name":"Smith","role":["user","admin"]}注意 它可用于验证引用令牌(如果消费者不支持适当的JWT或加密库,则可以使用JWT)。内省端点需要身份验证-因为内省端点的客户端是API,您可以在上配置密码ApiResource。
POST/connect/introspectAuthorization:Basicxxxyyytoken=
成功的响应将返回状态代码200以及活动或非活动令牌: {"active":true,"sub":"123"}未知或过期的令牌将被标记为无效:
POST/connect/revocationHTTP/1.1Host:server.example.comContent-Type:application/x-www-form-urlencodedAuthorization:BasicczZCaGRSa3F0MzpnWDFmQmF0M2JWtoken=45ghiukldjahdnhzdauz&token_type_hint=refresh_token注意
id_token_hint
当用户被重定向到端点时,系统会提示他们是否真的想要注销。发送从身份验证收到的原始id_token的客户端可以绕过此提示。这是作为查询的字符串参数传递的id_token_hint。
post_logout_redirect_uri
州
如果post_logout_redirect_uri传递了有效,则客户端也可以发送state参数。在用户重定向回客户端后,这将作为查询字符串参数返回给客户端。这通常由客户端用于跨重定向的往返状态。
此类为身份资源建模。
在简单的情况下,API只有一个范围。但是在某些情况下,您可能希望细分API的功能,并让不同的客户端访问不同的部分。
只是关于为ApiResource类提供的构造函数的注释。
要完全控制数据ApiResource,请使用不带参数的默认构造函数。如果要为每个API配置多个范围,可以使用此方法。例如:
newApiResource{Name="api2",Scopes={newScope(){Name="api2.full_access",DisplayName="FullaccesstoAPI2"},newScope{Name="api2.read_only",DisplayName="ReadonlyaccesstoAPI2"}}}对于每个API只需要一个范围的简单方案,则提供了几个接受a的便捷构造函数name。例如:
newApiResource("api1","SomeAPI1")使用便捷构造函数等同于:
newApiResource{Name="api1",DisplayName="SomeAPI1",Scopes={newScope(){Name="api1",DisplayName="SomeAPI1"}}}客户该Client级车型的ID连接或OAuth2.0用户端-例如,本地应用,Web应用程序或基于JS的应用程序。
最常见的用法是使用身份(成功案例)新建它:
context.Result=newGrantValidationResult(subject:"818727",authenticationMethod:"custom",claims:optionalClaims);...或使用错误和描述(失败案例):
context.Result=newGrantValidationResult(TokenRequestErrors.InvalidGrant,"invalidcustomcredential");在这两种情况下,您都可以传递将包含在令牌响应中的其他自定义值。
对要确定的请求建模是当前允许用户获取令牌。它包含以下属性:
该IIdentityServerInteractionService接口旨在提供用户界面用于与IdentityServer通信的服务,主要涉及用户交互。它可以从依赖注入系统获得,通常作为构造函数参数注入到IdentityServer的用户界面的MVC控制器中。
默认情况下,所有端点都已启用,但您可以通过禁用不需要的端点来锁定服务器。
该CustomEntries字典允许向发现文档添加自定义元素。
允许设置各种协议参数的长度限制,如客户端ID,范围,重定向URI等。
这些设置仅在启动时在服务配置中启用了相应的缓存时才适用。
IdentityServer支持某些端点的CORS。底层CORS实现由ASP.NETCore提供,因此它在依赖注入系统中自动注册。
在适当的情况下,IdentityServer会为某些响应发出CSP标头。
为IdentityServer中的配置和操作数据扩展点提供了基于EntityFramework的实现。EntityFramework的使用允许任何EF支持的数据库与此库一起使用。
此库提供的功能分为两个主要区域:配置存储和操作存储支持。根据托管应用程序的需要,这两个不同的区域可以独立使用或一起使用。
如果希望从EF支持的数据库加载客户端,标识资源,API资源或CORS数据(而不是使用内存配置),则可以使用配置存储。此支持提供了IClientStore和IResourceStore,以及ICorsPolicyService可扩展性点的实现。这些实现使用一个DbContext被调用的类ConfigurationDbContext来为数据库中的表建模。
要使用配置存储支持,请AddConfigurationStore在调用后使用扩展方法AddIdentityServer:
publicIServiceProviderConfigureServices(IServiceCollectionservices){conststringconnectionString=@"DataSource=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.EntityFramework-2.0.0;trusted_connection=yes;";varmigrationsAssembly=typeof(Startup).GetTypeInfo().Assembly.GetName().Name;services.AddIdentityServer()//thisaddstheconfigdatafromDB(clients,resources,CORS).AddConfigurationStore(options=>{options.ConfigureDbContext=builder=>builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly));});}要配置配置存储,请使用ConfigurationStoreOptions传递给配置回调的options对象。
此选项类包含用于控制配置存储的属性ConfigurationDbContext。
要使用操作商店支持,请AddOperationalStore在调用后使用扩展方法AddIdentityServer:
publicIServiceProviderConfigureServices(IServiceCollectionservices){conststringconnectionString=@"DataSource=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.EntityFramework-2.0.0;trusted_connection=yes;";varmigrationsAssembly=typeof(Startup).GetTypeInfo().Assembly.GetName().Name;services.AddIdentityServer()//thisaddstheoperationaldatafromDB(codes,tokens,consents).AddOperationalStore(options=>{options.ConfigureDbContext=builder=>builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly));//thisenablesautomatictokencleanup.thisisoptional.options.EnableTokenCleanup=true;options.TokenCleanupInterval=30;//intervalinseconds});}要配置操作存储,请使用OperationalStoreOptions传递给配置回调的options对象。
此选项类包含用于控制操作存储的属性PersistedGrantDbContext。
跨不同版本的IdentityServer(以及EF支持)很可能会更改数据库架构以适应新的和不断变化的功能。
我们不为创建数据库或将数据从一个版本迁移到另一个版本提供任何支持。您需要以组织认为合适的任何方式管理数据库创建,架构更改和数据迁移。
要使用此库,请正常配置ASP.NET标识。然后AddAspNetIdentity在调用后使用扩展方法AddIdentityServer:
以下是一些在线,远程和课堂培训选项,以了解有关ASP.NET核心身份和IdentityServer4的更多信息。
这是我们为期三天的旗舰课程(包括广泛的实践实验室),我们作为会议,现场和远程的一部分提供。
PluralSight有一些关于身份,ASP.NETCore和IdentityServer的好课程。