相关文章推荐
慷慨大方的红酒  ·  SpringBoot ...·  3 月前    · 
销魂的棒棒糖  ·  Java ...·  1 年前    · 

它在您的应用程序中启用以下功能:

认证即服务

适用于所有应用程序(Web,本机,移动设备,服务)的集中登录逻辑和工作流程。IdentityServer是OpenID Connect 的官方认证实现。

单点登录/注销

在多种应用程序类型上单点登录(和退出)。

API的访问控制

为各种类型的客户端发出API访问令牌,例如服务器到服务器,Web应用程序,SPA和本机/移动应用程序。

支持Azure Active Directory,Google,Facebook等外部身份提供商。这可以保护您的应用程序免受如何连接到这些外部提供商的详细信息的影响。

专注于定制

最重要的部分 - IdentityServer的许多方面都可以根据 您的 需求进行定制。由于IdentityServer是一个框架而不是盒装产品或SaaS,因此您可以编写代码以使系统适应您的方案。

成熟的开源

IdentityServer使用允许的Apache 2许可证,允许在其上构建商业产品。它也是.NET Foundation的一部分,它提供治理和法律支持。

免费和商业支持

如果您需要帮助构建或运行您的身份平台,请告知我们。我们可以通过多种方式为您提供帮助。

大多数现代应用程序或多或少看起来像这样:

最常见的互动是:

浏览器与Web应用程序通信

Web应用程序与Web API进行通信(有时是自己的,有时是代表用户)

基于浏览器的应用程序与Web API通信

本机应用程序与Web API通信

基于服务器的应用程序与Web API通信

Web API与Web API进行通信(有时是自己的,有时是代表用户)

通常,每个层(前端,中间层和后端)都必须保护资源并实现身份验证和/或授权 - 通常针对同一个用户存储。

将这些基本安全功能外包给安全令牌服务可防止在这些应用程序和端点之间复制该功能。

重构应用程序以支持安全令牌服务会产生以下体系结构和协议:

这种设计将安全问题分为两部分:

当应用程序需要知道当前用户的身份时,需要进行身份验证。通常,这些应用程序代表该用户管理数据,并且需要确保该用户只能访问允许的数据。最常见的例子是(经典)Web应用程序 - 但是基于本机和JS的应用程序也需要身份验证。

最常见的身份验证协议是SAML2p,WS-Federation和OpenID Connect - SAML2p是最受欢迎和最广泛部署的。

OpenID Connect是三者中的最新产品,但被认为是未来,因为它具有最大的现代应用潜力。它是从一开始就为移动应用场景而构建的,旨在实现API友好。

API访问

应用程序有两种与API通信的基本方式 - 使用应用程序标识或委派用户的标识。有时两种方法都需要结合起来。

OAuth2是一种协议,允许应用程序从安全令牌服务请求访问令牌并使用它们与API通信。此委派降低了客户端应用程序和API的复杂性,因为身份验证和授权可以集中。

OpenID Connect和OAuth 2.0 - 更好地结合在一起

OpenID Connect和OAuth 2.0非常相似 - 事实上,OpenID Connect是OAuth 2.0之上的扩展。两个基本的安全问题,即身份验证和API访问,被合并为一个协议 - 通常只需要一次往返安全令牌服务。

我们相信,OpenID Connect和OAuth 2.0的结合是在可预见的未来保护现代应用程序的最佳方法。IdentityServer4是这两种协议的实现,经过高度优化,可以解决当今移动,本机和Web应用程序的典型安全问题。

IdentityServer4如何提供帮助

IdentityServer是一个中间件,可将符合规范的OpenID Connect和OAuth 2.0端点添加到任意ASP.NET Core应用程序中。

通常,您构建(或重用)包含登录和注销页面的应用程序(并且可能同意 - 取决于您的需要),IdentityServer中间件为其添加必要的协议头,以便客户端应用程序可以与之通信使用那些标准协议。

托管应用程序可以像您想要的那样复杂,但我们通常建议通过仅包含与身份验证相关的UI来使攻击面尽可能小。

规范,文档和对象模型使用您应该注意的某些术语。

IdentityServer

IdentityServer是OpenID Connect提供程序 - 它实现OpenID Connect和OAuth 2.0协议。

不同的文献对同一个角色使用不同的术语 - 您可能还会找到安全令牌服务,身份提供者,授权服务器,IP-STS等。

但它们完全相同:一种向客户发放安全令牌的软件。

IdentityServer具有许多作业和功能 - 包括:

保护你的资源

使用本地帐户存储或外部身份提供程序对用户进行身份验证

提供会话管理和单点登录

管理和验证客户端

向客户发放身份和访问令牌

用户是使用注册客户端访问资源的人。

客户端是从IdentityServer请求令牌的软件 - 用于验证用户(请求身份令牌)或访问资源(请求访问令牌)。客户端必须首先向IdentityServer注册,然后才能请求令牌。

客户端的示例包括Web应用程序,本机移动或桌面应用程序,SPA,服务器进程等。

您希望使用IdentityServer保护资源 - 用户的身份数据或API。

每个资源都有一个唯一的名称 - 客户端使用此名称来指定他们希望访问哪些资源。

身份数据 关于用户的身份信息(也称为声明),例如姓名或电子邮件地址。

API API资源表示客户端要调用的功能 - 通常建模为Web API,但不一定。

身份令牌表示身份验证过程的结果。它至少包含用户的标识符(称为sub aka subject声明)以及有关用户如何以及何时进行身份验证的信息。它可以包含其他身份数据。

访问令牌允许访问API资源。客户端请求访问令牌并将其转发给API。访问令牌包含有关客户端和用户(如果存在)的信息。API使用该信息来授权访问其数据。

支持的规格

IdentityServer实现以下规范:

OpenID Connect Core 1.0(规范)

OpenID Connect Discovery 1.0(规范)

OpenID Connect会话管理1.0 - 草案28(规范)

OpenID Connect Front-Channel Logout 1.0 - 草案02(规范)

OpenID Connect Back-Channel Logout 1.0 - 草案04(规范)

OAuth 2.0用户

OAuth 2.0(RFC 6749)

OAuth 2.0承载令牌使用(RFC 6750)

OAuth 2.0多种响应类型(规范)

OAuth 2.0表单后期响应模式(规范)

OAuth 2.0令牌撤销(RFC 7009)

OAuth 2.0令牌自省(RFC 7662)

代码交换的证明密钥(RFC 7636)

用于客户端身份验证的JSON Web令牌(RFC 7523)

打包和构建

IdentityServer由许多nuget包组成。

IdentityServer4

nuget | github上

包含核心IdentityServer对象模型,服务和中间件。仅包含对内存配置和用户存储的支持 - 但您可以通过配置插入对其他存储的支持。这是其他回购和包装的内容。

github上

包含一个简单的入门UI,包括登录,注销和同意页面。

访问令牌验证处理程序

nuget | github上

用于验证API中令牌的ASP.NET Core身份验证处理程序。处理程序允许在同一API中支持JWT和引用令牌。

ASP.NET核心标识

nuget | github上

IdentityServer的ASP.NET核心身份集成包。该软件包提供了一个简单的配置API,可以为IdentityServer用户使用ASP.NET身份管理库。

EntityFramework核心

nuget | github上

EntityFramework IdentityServer的核心存储实现。此程序包为IdentityServer中的配置和操作存储提供EntityFramework实现。

此外,我们将开发/临时构建发布到MyGet。如果要尝试尝试,请将以下Feed添加到Visual Studio:

https://www.myget.org/F/identity/

支持和咨询选项

我们为IdentityServer提供了多种免费和商业支持和咨询选项。

免费支持是基于社区的,并使用公共论坛

有越来越多的人使用IdentityServer来监控StackOverflow上的问题。如果时间允许,我们也会尝试回答尽可能多的问题

您可以使用此Feed订阅所有IdentityServer4相关问题:

https://stackoverflow.com/questions/tagged/?tagnames=identityserver4&sort=newest

IdentityServer4 在提出新问题时请使用标签

您可以在我们的Gitter聊天室中与其他IdentityServer4用户聊天:

https://gitter.im/IdentityServer/IdentityServer4

如果您认为自己发现了错误或意外行为,请在Github 问题跟踪器上打开一个问题。我们会尽快回复您。请理解我们也有日常工作,可能太忙而无法立即回复。

在发布之前还要检查贡献指南。

我们正在围绕身份和访问控制架构进行咨询,指导和定制软件开发,特别是IdentityServer。请取得联系与我们共同探讨可行方案。

我们经常围绕现代应用的身份和访问控制进行研讨会。在这里查看议程和即将公布的日期 。我们也可以在贵公司私下进行培训。 联系我们以请求现场培训。

Admin UI,Identity Express和SAML2p支持

我们的合作伙伴提供了几种商业附加产品,请访问https://www.identityserver.com/products/。

演示服务器和测试

您可以使用您喜欢的客户端库尝试IdentityServer4。我们在demo.identityserver.io上有一个测试实例。在主页面上,您可以找到有关如何配置客户端以及如何调用API的说明。

此外,我们还有一个repo,可以运行各种IdentityServer和Web API组合(IdentityServer 3和4,ASP.NET Core和Katana)。我们使用此测试工具确保所有排列都有效。您可以通过克隆此 repo来自行测试。

我们对社区贡献非常开放,但您应该遵循一些指导方针,以便我们可以毫不费力地处理这个问题。

如何贡献?

最简单的贡献方式是打开一个问题并开始讨论。然后我们可以决定是否以及如何实现功能或更改。如果您应该提交带有代码更改的pull请求,请从描述开始,只进行最小的更改并提供涵盖这些更改的测试。

首先阅读:成为一名优秀的开源公民

一般反馈和讨论?

请开始讨论核心回购问题跟踪器。

IdentityServer是针对ASP.NET Core 2构建的,可在.NET Framework 4.6.1(及更高版本)和.NET Core 2(及更高版本)上运行。

错误和功能请求?

请在相应的GitHub仓库中记录一个新问题:

AccessTokenValidation

https://gitter.im/IdentityServer/IdentityServer4

贡献代码和内容

在您提供任何代码或内容之前,您需要签署贡献者许可协议。这是一个自动过程,将在您打开拉取请求后启动。

我们只接受开发分支的PR。

如果您启动贡献项目(例如,支持Database X或Configuration Store Y),我们非常感谢。告诉我们,我们可以在我们的文档中发推文和链接。

我们通常不想拥有这些贡献库,我们已经非常忙于支持核心项目。

截至2017年10月,IdentityServer4。* nuget名称空间保留给我们的软件包。请使用以下命名约定:

YourProjectName.IdentityServer4

IdentityServer4.Contrib.YourProjectName

设置和概述

启动新IdentityServer项目有两种基本方法:

从Visual Studio中的ASP.NET标识模板开始

如果您从头开始,我们提供了几个帮助程序和内存存储,因此您不必担心从一开始就存在持久性。

如果您从ASP.NET身份开始,我们也提供了一种简单的方法来集成它。

快速入门提供了各种常见IdentityServer方案的分步说明。他们从绝对的基础开始,变得更加复杂 - 建议你按顺序完成它们。

每个快速入门都有一个参考解决方案 - 您可以 在quickstarts文件夹中的IdentityServer4.Samples仓库中找到代码 。

屏幕截图显示了Visual Studio - 但这不是必需的。

创建快速入门IdentityServer

首先创建一个新的ASP.NET Core项目。

然后选择“清空”选项。

接下来,添加IdentityServer4 nuget包:

或者,您可以使用程序包管理器控制台通过运行以下命令来添加依赖项:

“安装包IdentityServer4”

IdentityServer构建编号1.x目标ASP.NET Core 1.1,IdentityServer构建编号2.x目标ASP.NET Core 2.0。

IdentityServer使用通常的模式为ASP.NET Core主机配置和添加服务。在 ConfigureServices 所需的服务中配置并添加到DI系统。在 Configure 中间件中添加到HTTP管道。

Startup.cs 文件修改为如下所示:

public class Startup
{
   public void ConfigureServices(IServiceCollection services)
  {
       services.AddIdentityServer()
          .AddDeveloperSigningCredential();
  }

   public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
       if (env.IsDevelopment())
      {
           app.UseDeveloperExceptionPage();
      }

       app.UseIdentityServer();
  }
}

AddIdentityServer 在DI中注册IdentityServer服务。它还为运行时状态注册内存存储。这对于开发方案很有用。对于生产方案,您需要一个持久性或共享存储,如数据库或缓存。有关详细信息,请参阅EntityFramework快速入门。

AddDeveloperSigningCredential 扩展程序为签名令牌创建临时密钥材料。同样,这可能对入门有用,但需要替换为生产场景的一些持久性密钥材料。有关更多信息,请参阅加密文档。

IdentityServer尚未准备好启动。我们将在以下快速入门中添加所需的服务。

默认情况下,Visual Studio使用IIS Express来托管您的Web项目。这完全没问题,除了您将无法看到控制台的实时日志输出。

IdentityServer广泛使用日志记录,而UI中的“可见”错误消息或返回给客户端是故意模糊的。

我们建议在控制台主机中运行IdentityServer。您可以通过在Visual Studio中切换启动配置文件来完成此操作。每次启动IdentityServer时也不需要启动浏览器 - 您也可以关闭它:

此外,在这些快速入门的一致URL上运行IdentityServer会很有帮助。您还应该在上面的启动配置文件对话框中配置此URL,然后使用 http://localhost:5000/ 。在上面的屏幕截图中,您可以看到此URL已配置。

我们建议为IIS Express和自托管配置相同的端口。这样,您可以在两者之间切换,而无需修改客户端中的任何配置。

要在启动时选择控制台主机,必须在Visual Studio的启动菜单中选择它:

如何运行快速入门

如上所述,每个快速入门都有一个参考解决方案 - 您可以 在quickstarts文件夹中的IdentityServer4.Samples repo中找到代码 。

运行快速入门解决方案各个部分的最简单方法是将启动模式设置为“当前选择”。右键单击解决方案并选择“设置启动项目”:

通常,首先启动IdentityServer,然后启动API,然后启动客户端。如果您确实想要调试,只能在调试器中运行。否则Ctrl + F5是运行项目的最佳方式。

使用客户端凭据保护

本快速入门介绍了使用IdentityServer保护API的最基本方案。

在这种情况下,我们将定义一个API和一个想要访问它的客户端。客户端将在IdentityServer请求访问令牌并使用它来获取对API的访问权限。

范围定义了您要保护的系统中的资源,例如API。

由于我们在本演练中使用内存配置 - 您只需创建一个类型的对象 ApiResource 并设置适当的属性即可。

将文件(例如 Config.cs )添加到项目中并添加以下代码:

public static IEnumerable<ApiResource> GetApiResources()
{
   return new List<ApiResource>
  {
       new ApiResource("api1", "My API")
  };
}

定义客户端

下一步是定义可以访问此API的客户端。

对于此方案,客户端将不具有交互式用户,并将使用IdentityServer的所谓客户端密钥进行身份验证。将以下代码添加到Config.cs文件中:

public static IEnumerable<Client> GetClients()
{
   return new List<Client>
  {
       new Client
      {
           ClientId = "client",

           // no interactive user, use the clientid/secret for authentication
           AllowedGrantTypes = GrantTypes.ClientCredentials,

           // secret for authentication
           ClientSecrets =
          {
               new Secret("secret".Sha256())
          },

           // scopes that client has access to
           AllowedScopes = { "api1" }
      }
  };
}

配置IdentityServer

要将IdentityServer配置为使用范围和客户端定义,您需要向 ConfigureServices 方法添加代码。您可以使用方便的扩展方法 - 在封面下,这些将相关的存储和数据添加到DI系统中:

public void ConfigureServices(IServiceCollection services)
{
   // configure identity server with in-memory stores, keys, clients and resources
   services.AddIdentityServer()
      .AddDeveloperSigningCredential()
      .AddInMemoryApiResources(Config.GetApiResources())
      .AddInMemoryClients(Config.GetClients());
}

就是这样 - 如果您运行服务器并浏览浏览器 http://localhost:5000/.well-known/openid-configuration ,您应该会看到所谓的发现文档。客户端和API将使用它来下载必要的配置数据。

接下来,为您的解决方案添加API。

您可以使用ASP.NET Core Web API模板。同样,我们建议您控制端口并使用与以前配置Kestrel和启动配置文件相同的技术。本演练假定您已将API配置为运行 http://localhost:5001

向API项目添加新控制器:

[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
  [HttpGet]
   public IActionResult Get()
  {
       return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
  }
}

稍后将使用此控制器来测试授权要求,以及通过API的眼睛可视化声明身份。

最后一步是将身份验证服务添加到DI和身份验证中间件到管道。这些将:

验证传入令牌以确保它来自受信任的颁发者

验证令牌是否有效用于此api(aka范围)

将IdentityServer4.AccessTokenValidation NuGet包添加到项目中。

将Startup更新为如下所示:

public class Startup
{
   public void ConfigureServices(IServiceCollection services)
  {
       services.AddMvcCore()
          .AddAuthorization()
          .AddJsonFormatters();

       services.AddAuthentication("Bearer")
          .AddIdentityServerAuthentication(options =>
          {
               options.Authority = "http://localhost:5000";
               options.RequireHttpsMetadata = false;

               options.ApiName = "api1";
          });
  }

   public void Configure(IApplicationBuilder app)
  {
       app.UseAuthentication();

       app.UseMvc();
  }
}

AddAuthentication 将身份验证服务添加到DI并配置 "Bearer" 为默认方案。 AddIdentityServerAuthentication 将IdentityServer访问令牌验证处理程序添加到DI中以供身份验证服务使用。 UseAuthentication 将身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。

如果您使用浏览器导航到控制器( http://localhost:5001/identity ),您应该获得401状态代码作为回报。这意味着您的API需要凭据。

就是这样,API现在受到IdentityServer的保护。

创建客户端

最后一步是编写请求访问令牌的客户端,然后使用此令牌访问API。为此,请向您的解决方案添加一个控制台项目(请参阅此处的完整代码)。

IdentityServer的令牌端点实现OAuth 2.0协议,您可以使用原始HTTP来访问它。但是,我们有一个名为IdentityModel的客户端库,它将协议交互封装在一个易于使用的API中。

将IdentityModel NuGet包添加到您的应用程序。

IdentityModel包括用于发现端点的客户端库。这样您只需要知道IdentityServer的基地址 - 可以从元数据中读取实际的端点地址:

// discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError)
{
  Console.WriteLine(disco.Error);
  return;
}

接下来,您可以使用 TokenClient 该类来请求令牌。要创建实例,您需要传递令牌端点地址,客户端ID和密码。

接下来,您可以使用该 RequestClientCredentialsAsync 方法为您的API请求令牌:

// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");

if (tokenResponse.IsError)
{
   Console.WriteLine(tokenResponse.Error);
   return;
}

Console.WriteLine(tokenResponse.Json);

将访问令牌从控制台复制并粘贴到jwt.io以检查原始令牌。

最后一步是调用API。

要将访问令牌发送到API,通常使用HTTP Authorization标头。这是使用 SetBearerToken 扩展方法完成的:

// call api
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);

var response = await client.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
   Console.WriteLine(response.StatusCode);
}
else
{
   var content = await response.Content.ReadAsStringAsync();
   Console.WriteLine(JArray.Parse(content));
}

输出应如下所示:

默认情况下,访问令牌将包含有关范围,生命周期(nbf和exp),客户端ID(client_id)和颁发者名称(iss)的声明。

进一步的实验

本演练重点关注目前的成功之路

客户端能够请求令牌

客户端可以使用令牌来访问API

您现在可以尝试激发错误以了解系统的行为,例如

尝试在未运行时连接到IdentityServer(不可用)

尝试使用无效的客户端ID或机密来请求令牌

尝试在令牌请求期间请求无效范围

尝试在API未运行时调用API(不可用)

不要将令牌发送到API

将API配置为需要与令牌中的范围不同的范围

使用密码保护

OAuth 2.0资源所有者密码授予允许客户端向令牌服务发送用户名和密码,并获取代表该用户的访问令牌。

规范建议仅对“受信任”(或遗留)应用程序使用资源所有者密码授予。一般来说,当您想要对用户进行身份验证并请求访问令牌时,通常会更好地使用其中一个交互式OpenID Connect流程。

尽管如此,这种授权类型允许我们将用户的概念引入我们的快速启动IdentityServer,这就是我们展示它的原因。

就像资源(也称为范围)和客户端的内存存储一样,用户也有一个。

有关如何正确存储和管理用户帐户的详细信息,请查看基于ASP.NET身份的快速入门。

该类 TestUser 代表测试用户及其声明。让我们通过在config类中添加以下代码来创建几个用户:

首先将以下using语句添加到 Config.cs 文件中:

using IdentityServer4.Test;

public static List<TestUser> GetUsers()
{
   return new List<TestUser>
  {
       new TestUser
      {
           SubjectId = "1",
           Username = "alice",
           Password = "password"
      },
       new TestUser
      {
           SubjectId = "2",
           Username = "bob",
           Password = "password"
      }
  };
}

然后使用IdentityServer注册测试用户:

public void ConfigureServices(IServiceCollection services)
{
   // configure identity server with in-memory stores, keys, clients and scopes
   services.AddIdentityServer()
      .AddDeveloperSigningCredential()
      .AddInMemoryApiResources(Config.GetApiResources())
      .AddInMemoryClients(Config.GetClients())
      .AddTestUsers(Config.GetUsers());
}

AddTestUsers 扩展方法做了几件事情引擎盖下

添加对资源所有者密码授予的支持

添加对登录UI通常使用的用户相关服务的支持(我们将在下一个快速入门中使用它)

添加对基于测试用户的配置文件服务的支持(您将在下一个快速入门中了解更多信息)

为资源所有者密码授予添加客户端

您可以通过更改 AllowedGrantTypes 属性来简单地向现有客户端添加对授权类型的支持 。如果您需要您的客户端能够使用绝对支持的两种授权类型。

通常,您希望为资源所有者用例创建单独的客户端,将以下内容添加到客户端配置中:

public static IEnumerable<Client> GetClients()
{
   return new List<Client>
  {
       // other clients omitted...

       // resource owner password grant client
       new Client
      {
           ClientId = "ro.client",
           AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

           ClientSecrets =
          {
               new Secret("secret".Sha256())
          },
           AllowedScopes = { "api1" }
      }
  };
}

使用密码授权请求令牌

客户端看起来与我们为客户端凭据授予所做的非常相似。主要区别在于客户端会以某种方式收集用户的密码,并在令牌请求期间将其发送到令牌服务。

IdentityModel再次 TokenClient 可以在这里提供帮助:

// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password", "api1");

if (tokenResponse.IsError)
{
   Console.WriteLine(tokenResponse.Error);
   return;
}

Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");

将令牌发送到身份API端点时,您会注意到与客户端凭据授权相比有一个小但重要的区别。访问令牌现在将包含 sub 唯一标识用户的声明。通过在调用API之后检查内容变量可以看到这个“子”声明,并且控制器应用程序也会在屏幕上显示该声明。

声明的存在(或不存在) sub 允许API区分代表客户的呼叫和代表用户的呼叫。

使用OpenID Connect添加用户认证

在本快速入门中,我们希望通过OpenID Connect协议为我们的IdentityServer添加对交互式用户身份验证的支持。

一旦到位,我们将创建一个将使用IdentityServer进行身份验证的MVC应用程序。

OpenID Connect所需的所有协议支持已内置于IdentityServer中。您需要为登录,注销,同意和错误提供必要的UI部件。

虽然外观和精确的工作流程在每个IdentityServer实现中可能总是不同,但我们提供了一个基于MVC的示例UI,您可以将其用作起点。

可以在快速入门UI存储库中找到此UI 。您可以克隆或下载此repo,并将控制器,视图,模型和CSS放入IdentityServer Web应用程序中。

或者,您可以从与IdentityServer Web应用程序相同的目录中的命令行运行此命令,以自动执行下载:

iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))

对于Unix / Linux:

\curl -L https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.sh | bash

添加MVC UI资产后,您还需要在DI系统和管道中将MVC添加到托管应用程序。 ConfigureServices 使用 AddMvc 扩展方法添加MVC :

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();

   // configure identity server with in-memory stores, keys, clients and scopes
   services.AddIdentityServer()
      .AddDeveloperSigningCredential()
      .AddInMemoryApiResources(Config.GetApiResources())
      .AddInMemoryClients(Config.GetClients())
      .AddTestUsers(Config.GetUsers());
}

Configure 使用 UseMvc 扩展方法将MVC添加为管道中的最后一个中间件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
  {
       app.UseDeveloperExceptionPage();
  }

   app.UseIdentityServer();

   app.UseStaticFiles();
   app.UseMvcWithDefaultRoute();
}

有关详细信息,请参阅快速入门UI 的自述文件。

release UI repo 的分支具有与最新稳定版本匹配的UI。该 dev 分支与IdentityServer4的当前开发版本一起使用。如果您正在寻找特定版本的UI - 请检查标签。

花一些时间检查控制器和模型,您越了解它们,就越容易进行未来的修改。大多数代码使用“功能文件夹”样式存在于“Quickstart”文件夹中。如果此样式不适合您,请随意以您想要的任何方式组织代码。

创建MVC客户端

接下来,您将向您的解决方案添加MVC应用程序。使用ASP.NET Core“Web应用程序”(即MVC)模板。不要在向导中配置“身份验证”设置 - 您将在此快速入门中手动执行此操作。创建项目后,将应用程序配置为使用端口5002(有关如何执行此操作的说明,请参阅概述部分)。

要为ID连接的认证支持添加到了MVC应用程序,添加以下内容 ConfigureServices Startup

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();

   JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

   services.AddAuthentication(options =>
      {
           options.DefaultScheme = "Cookies";
           options.DefaultChallengeScheme = "oidc";
      })
      .AddCookie("Cookies")
      .AddOpenIdConnect("oidc", options =>
      {
           options.SignInScheme = "Cookies";

           options.Authority = "http://localhost:5000";
           options.RequireHttpsMetadata = false;

           options.ClientId = "mvc";
           options.SaveTokens = true;
      });
}

AddAuthentication 将身份验证服务添加到DI。作为主装置来验证用户(通过我们使用一个cookie "Cookies" DefaultScheme )。我们设置为 DefaultChallengeScheme to, "oidc" 因为当我们需要用户登录时,我们将使用OpenID Connect方案。

然后 AddCookie ,我们使用添加可以处理cookie的处理程序。

最后, AddOpenIdConnect 用于配置执行OpenID Connect协议的处理程序。这 Authority 表明我们信任IdentityServer。然后我们通过 ClientId 。识别这个客户。 SignInScheme 用于在OpenID Connect协议完成后使用cookie处理程序发出cookie。并且 SaveTokens 用于在cookie中保留来自IdentityServer的令牌(因为稍后将需要它们)。

同样,我们已经关闭了JWT声明类型映射,以允许众所周知的声明(例如“sub”和“idp”)流畅地通过:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

然后要确保认证服务执行对每个请求,加入 UseAuthentication Configure Startup

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
  {
       app.UseDeveloperExceptionPage();
  }
   else
  {
       app.UseExceptionHandler("/Home/Error");
  }

   app.UseAuthentication();

   app.UseStaticFiles();
   app.UseMvcWithDefaultRoute();
}

应该在管道中的MVC之前添加认证中间件。

最后一步是触发身份验证握手。为此,请转到主控制器并添加 [Authorize] 其中一个操作。同时修改该操作的视图以显示用户的声明,例如:

<dl>
   @foreach (var claim in User.Claims)
  {
       <dt>@claim.Type</dt>
       <dd>@claim.Value</dd>
  }
</dl>

如果您现在使用浏览器导航到该控制器,将尝试重定向到IdentityServer - 这将导致错误,因为MVC客户端尚未注册。

添加对OpenID Connect标识范围的支持

与OAuth 2.0类似,OpenID Connect也使用范围概念。同样,范围代表您想要保护的内容以及客户想要访问的内容。与OAuth相比,OIDC中的范围不代表API,而是代表用户ID,名称或电子邮件地址等身份数据。

通过添加新助手(in )来创建对象集合,添加对标准 openid (subject id)和 profile (名字,姓氏等)范围的支持: Config.cs``IdentityResource

public static IEnumerable<IdentityResource> GetIdentityResources()
{
  return new List<IdentityResource>
  {
      new IdentityResources.OpenId(),
      new IdentityResources.Profile(),
  };
}

所有标准范围及其相应的声明都可以在OpenID Connect 规范中找到

然后,您需要将这些标识资源添加到IdentityServer配置中 Startup.cs 。使用 AddInMemoryIdentityResources 您调用的扩展方法 AddIdentityServer()

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();

  // configure identity server with in-memory stores, keys, clients and scopes
  services.AddIdentityServer()
      .AddDeveloperSigningCredential()
      .AddInMemoryIdentityResources(Config.GetIdentityResources())
      .AddInMemoryApiResources(Config.GetApiResources())
      .AddInMemoryClients(Config.GetClients())
      .AddTestUsers(Config.GetUsers());
}

为OpenID Connect隐式流添加客户端

最后一步是将MVC客户端的新配置条目添加到IdentityServer。

基于OpenID Connect的客户端与我们目前添加的OAuth 2.0客户端非常相似。但由于OIDC中的流程始终是交互式的,因此我们需要在配置中添加一些重定向URL。

将以下内容添加到客户端配置中:

public static IEnumerable<Client> GetClients()
{
  return new List<Client>
  {
      // other clients omitted...

      // OpenID Connect implicit flow client (MVC)
      new Client
      {
          ClientId = "mvc",
          ClientName = "MVC Client",
          AllowedGrantTypes = GrantTypes.Implicit,

          // where to redirect to after login
          RedirectUris = { "http://localhost:5002/signin-oidc" },

          // where to redirect to after logout
          PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

          AllowedScopes = new List<string>
          {
              IdentityServerConstants.StandardScopes.OpenId,
              IdentityServerConstants.StandardScopes.Profile
          }
      }
  };
}

测试客户端

现在终于应该为新的MVC客户端做好一切准备。

通过导航到受保护的控制器操作来触发身份验证握手。您应该会看到重定向到IdentityServer的登录页面。

成功登录后,将向用户显示同意屏幕。在这里,用户可以决定是否要将他的身份信息发布到客户端应用程序。

可以使用 RequireConsent 客户端对象上的属性基于每个客户端关闭同意。

..最后,浏览器重定向回客户端应用程序,显示用户的声明。

在开发期间,您有时可能会看到一个异常,指出无法验证令牌。这是因为签名密钥材料是在运行中创建的,并且仅保留在内存中。当客户端和IdentityServer不同步时会发生此异常。只需在客户端重复操作,下次元数据赶上时,一切都应该再次正常工作。

最后一步是向MVC客户端添加注销。

使用IdentityServer等身份验证服务,仅清除本地应用程序cookie是不够的。此外,您还需要向IdentityServer进行往返以清除中央单点登录会话。

确切的协议步骤在OpenID Connect中间件中实现,只需将以下代码添加到某个控制器即可触发注销:

public async Task Logout()
{
   await HttpContext.SignOutAsync("Cookies");
   await HttpContext.SignOutAsync("oidc");
}

这将清除本地cookie,然后重定向到IdentityServer。IdentityServer将清除其cookie,然后为用户提供返回MVC应用程序的链接。

进一步的实验

如上所述,OpenID Connect中间件默认要求 配置文件 范围。此范围还包括 名称 网站等声明

让我们将这些声明添加到用户,以便IdentityServer可以将它们放入身份标记:

public static List<TestUser> GetUsers()
{
   return new List<TestUser>
  {
       new TestUser
      {
           SubjectId = "1",
           Username = "alice",
           Password = "password",

           Claims = new []
          {
               new Claim("name", "Alice"),
               new Claim("website", "https://alice.com")
          }
      },
       new TestUser
      {
           SubjectId = "2",
           Username = "bob",
           Password = "password",

           Claims = new []
          {
               new Claim("name", "Bob"),
               new Claim("website", "https://bob.com")
          }
      }
  };
}

下次进行身份验证时,您的声明页面现在会显示其他声明。

随意添加更多声明 - 以及更多范围。在 Scope 对ID连接中间件属性可以在其中配置的作用域认证期间将发送到IdentityServer。

值得注意的是,对令牌声明的检索是一个可扩展性点 - IProfileService 。由于我们正在使用 AddTestUsers TestUserProfileService 默认使用。您可以在此处检查源代码 以查看其工作原理。

添加对外部认证的支持

接下来,我们将添加对外部认证的支持。这非常简单,因为您真正需要的是ASP.NET Core兼容的身份验证处理程序。

ASP.NET Core本身支持Google,Facebook,Twitter,Microsoft Account和OpenID Connect。此外,你可以找到很多其他的认证供应商实现在这里。

添加Google支持

要使用Google进行身份验证,首先需要向他们注册。这是在他们的开发者控制台完成的。通过将 / signin-google 路径添加到您的基地址(例如http:// localhost:5000 / signin-google),创建一个新项目,启用Google+ API并配置您本地IdentityServer的回调地址。

如果您在端口5000上运行 - 您只需使用下面代码段中的客户端ID / secret,因为这是我们预先注册的。

首先将Google身份验证处理程序添加到DI。这是通过添加该代码段完成 ConfigureServices Startup

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();

   // configure identity server with in-memory stores, keys, clients and scopes
   services.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 下的类。

现在运行MVC客户端并尝试进行身份验证 - 您将在登录页面上看到一个Google按钮:

身份验证后,您可以看到声明现在来自Google数据。

进一步的实验

您可以添加其他外部提供程序。我们有一个云托管的IdentityServer4 演示版,您可以使用OpenID Connect进行集成。

将OpenId Connect处理程序添加到DI:

services.AddAuthentication()
  .AddGoogle("Google", options =>
  {
       options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

       options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com";
       options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo";
  })
  .AddOpenIdConnect("oidc", "OpenID Connect", options =>
  {
       options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
       options.SignOutScheme = IdentityServerConstants.SignoutScheme;

       options.Authority = "https://demo.identityserver.io/";
       options.ClientId = "implicit";

       options.TokenValidationParameters = new TokenValidationParameters
      {
           NameClaimType = "name",
           RoleClaimType = "role"
      };
  });

现在,用户应该能够使用云托管的演示标识提供程序。

快速入门UI自动配置外部用户。当外部用户首次登录时,将创建新的本地用户,并且所有外部声明都将复制并与新用户关联。你处理这种情况的方式完全取决于你。也许你想首先展示一些注册用户界面。可以在此处找到默认快速入门的源代码。可以在此处找到执行自动配置的控制器。

切换到混合流并添加API访问

在之前的快速入门中,我们探讨了API访问和用户身份验证。现在我们想把这两个部分放在一起。

OpenID Connect和OAuth 2.0组合的优点在于,您可以使用单个协议和使用令牌服务进行单次交换来实现这两者。

在之前的快速入门中,我们使用了OpenID Connect隐式流程。在隐式流程中,所有令牌都通过浏览器传输,这对于身份令牌来说是完全正确的。现在我们还想要一个访问令牌。

访问令牌比身份令牌更敏感,如果不需要,我们不希望将它们暴露给“外部”世界。OpenID Connect包含一个名为“混合流”的流程,它为我们提供了两全其美的优势,身份令牌通过浏览器渠道传输,因此客户端可以在进行任何更多工作之前对其进行验证。如果验证成功,客户端会打开令牌服务的反向通道以检索访问令牌。

修改客户端配置

没有太多必要的修改。首先,我们希望允许客户端使用混合流,此外我们还希望客户端允许执行不在用户上下文中的服务器到服务器API调用(这与我们的客户端凭证快速启动非常相似)。这是使用该 AllowedGrantTypes 属性表示的。

接下来我们需要添加一个客户端密钥。这将用于检索反向通道上的访问令牌。

最后,我们还让客户端访问 offline_access 范围 - 这允许请求刷新令牌以实现长期存在的API访问:

new Client
{
   ClientId = "mvc",
   ClientName = "MVC Client",
   AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

   ClientSecrets =
  {
       new Secret("secret".Sha256())
  },

   RedirectUris           = { "http://localhost:5002/signin-oidc" },
   PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

   AllowedScopes =
  {
       IdentityServerConstants.StandardScopes.OpenId,
       IdentityServerConstants.StandardScopes.Profile,
       "api1"
  },
   AllowOfflineAccess = true
};

修改MVC客户端

MVC客户端的修改也很少 - ASP.NET Core OpenID Connect处理程序内置了对混合流的支持,因此我们只需要更改一些配置值。

我们配置 ClientSecret 匹配IdentityServer的秘密。添加 offline_access api1 范围,并设置 ResponseType 为(这基本上意味着“使用混合流”) code id_token

.AddOpenIdConnect("oidc", options =>
{
   options.SignInScheme = "Cookies";

   options.Authority = "http://localhost:5000";
   options.RequireHttpsMetadata = false;

   options.ClientId = "mvc";
   options.ClientSecret = "secret";
   options.ResponseType = "code id_token";

   options.SaveTokens = true;
   options.GetClaimsFromUserInfoEndpoint = true;

   options.Scope.Add("api1");
   options.Scope.Add("offline_access");
});

当您运行MVC客户端时,除了现在同意屏幕要求您提供额外的API和脱机访问范围之外,没有太大的区别。

使用访问令牌

OpenID Connect中间件会自动为您保存令牌(在我们的案例中为身份,访问和刷新)。这就是 SaveTokens 设置的作用。

从技术上讲,令牌存储在cookie的属性部分中。访问它们的最简单方法是使用 Microsoft.AspNetCore.Authentication 命名空间中的扩展方法。

例如,在您的声明视图中:

<dt>access token</dt>
<dd>@await ViewContext.HttpContext.GetTokenAsync("access_token")</dd>

<dt>refresh token</dt>
<dd>@await ViewContext.HttpContext.GetTokenAsync("refresh_token")</dd>

要使用访问令牌访问API,您需要做的就是检索令牌,并在 HttpClient 上设置它:

public async Task<IActionResult> CallApiUsingUserAccessToken()
{
   var accessToken = await HttpContext.GetTokenAsync("access_token");

   var client = new HttpClient();
   client.SetBearerToken(accessToken);
   var content = await client.GetStringAsync("http://localhost:5001/identity");

   ViewBag.Json = JArray.Parse(content).ToString();
   return View("json");
}
使用ASP.NET核心身份

IdentityServer旨在提供灵活性,其中一部分允许您为用户及其数据(包括密码)使用您想要的任何数据库。如果您从一个新的用户数据库开始,那么ASP.NET Identity是您可以选择的一个选项。本快速入门展示了如何将Identity Identity与IdentityServer一起使用。

本快速入门假设您已经完成了所有之前的快速入门。本快速入门使用ASP.NET标识的方法是从Visual Studio中的ASP.NET标识模板创建一个新项目。这个新项目将取代我们在之前的快速入门中从头开始构建的先前IdentityServer项目。此解决方案中的所有其他项目(针对客户端和API)将保持不变。

ASP.NET身份的新项目

第一步是为您的解决方案添加ASP.NET Identity的新项目。鉴于ASP.NET Identity需要大量代码,因此使用Visual Studio中的模板是有意义的。您最终将删除IdentityServer的旧项目(假设您正在关注其他快速入门),但是您需要迁移几个项目(或者按照之前的快速入门中的描述从头开始重写)。

首先创建一个新的“ASP.NET核心Web应用程序”项目。

然后选择“Web应用程序模板(模型 - 视图 - 控制器)”选项。

然后单击“更改身份验证”按钮,并选择“个人用户帐户”(这意味着使用ASP.NET身份):

最后,您的新项目对话框应该如下所示。完成后,单击“确定”以创建项目。

不要忘记修改托管(如此处所述)以在端口5000上运行。这很重要,因此现有客户端和api项目将继续工作。

添加IdentityServer包

添加 IdentityServer4.AspNetIdentity NuGet包。这取决于 IdentityServer4 包,因此会自动添加为传递依赖项。

范围和客户端配置

尽管这是IdentityServer的新项目,但我们仍需要与之前的快速入门相同的范围和客户端配置。将用于以前快速入门的配置类(在Config.cs中)复制到此新项目中。

必要的配置更改(暂时)是禁用MVC客户端的同意。我们还没有复制先前IdentityServer项目的同意代码,所以现在对MVC客户端进行一次修改并设置 RequireConsent=false

new Client
{
  ClientId = "mvc",
  ClientName = "MVC Client",
  AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

  RequireConsent = false,

  ClientSecrets =
  {
      new Secret("secret".Sha256())
  },

  RedirectUris           = { "http://localhost:5002/signin-oidc" },
  PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

  AllowedScopes =
  {
      IdentityServerConstants.StandardScopes.OpenId,
      IdentityServerConstants.StandardScopes.Profile,
      "api1"
  },
  AllowOfflineAccess = true
}

配置IdentityServer

和以前一样,IdentityServer需要在Startup.cs中 ConfigureServices 和in Configure 中配置。

ConfigureServices

这显示了为ASP.NET Identity生成的模板代码,以及IdentityServer所需的附加内容(最后)。在之前的快速入门中, AddTestUsers 扩展方法用于注册用户,但在这种情况下,我们将该扩展方法替换 AddAspNetIdentity 为使用ASP.NET Identity用户。该 AddAspNetIdentity 扩展方法需要一个通用的参数,它是你的ASP.NET身份用户类型(同一个在需要 AddIdentity 从模板方法)。

public void ConfigureServices(IServiceCollection services)
{
  services.AddDbContext<ApplicationDbContext>(options =>
      options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

  services.AddIdentity<ApplicationUser, IdentityRole>()
      .AddEntityFrameworkStores<ApplicationDbContext>()
      .AddDefaultTokenProviders();

  // Add application services.
  services.AddTransient<IEmailSender, EmailSender>();

  services.AddMvc();

  // configure identity server with in-memory stores, keys, clients and scopes
  services.AddIdentityServer()
      .AddDeveloperSigningCredential()
      .AddInMemoryPersistedGrants()
      .AddInMemoryIdentityResources(Config.GetIdentityResources())
      .AddInMemoryApiResources(Config.GetApiResources())
      .AddInMemoryClients(Config.GetClients())
      .AddAspNetIdentity<ApplicationUser>();
}

在使用ASP.NET标识时,在DI系统中 ASP.NET标识 之后 注册IdentityServer非常重要,因为IdentityServer会从ASP.NET标识覆盖某些配置。

这显示了为ASP.NET Identity生成的模板代码,以及 UseIdentityServer 替换调用的调用 UseAuthentication

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  if (env.IsDevelopment())
  {
      app.UseDeveloperExceptionPage();
      app.UseBrowserLink();
      app.UseDatabaseErrorPage();
  }
  else
  {
      app.UseExceptionHandler("/Home/Error");
  }

  app.UseStaticFiles();

  // app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
  app.UseIdentityServer();

  app.UseMvc(routes =>
  {
      routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
  });
}

创建用户数据库

鉴于这是一个新的ASP.NET Identity项目,您将需要创建数据库。您可以通过从项目目录运行命令提示符并运行来执行此操作,如下所示: dotnet ef database update -c ApplicationDbContext

此时,您应该能够运行项目并在数据库中创建/注册用户。启动应用程序,然后从主页单击“注册”链接:

在注册页面上创建一个新的用户帐户:

现在您拥有了一个用户帐户,您应该能够登录,使用客户端并调用API。

使用MVC客户端登录

启动MVC客户端应用程序,您应该能够单击“安全”链接以登录。

您应该被重定向到ASP.NET Identity登录页面。使用新创建的用户登录:

登录后,您应该跳过同意页面(根据我们上面做出的更改),并立即重定向回MVC客户端应用程序,在该应用程序中应列出您的用户声明。

您还应该能够单击“使用应用程序标识调用API”来代表用户调用API:

现在,您已使用ASP.NET Identity中的用户登录。

下一步是什么?

IdentityServer的先前快速入门项目提供了同意页面,错误页面和注销页面。这些缺失部分的代码可以简单地从之前的快速入门项目复制到此项目中。完成后,您最终可以删除/删除旧的IdentityServer项目。此外,一旦完成此操作,请不要忘记 RequireConsent=true 在MVC客户端配置上重新启用该标志。

此快速入门的示例代码已经为您完成了这些步骤,因此您可以快速开始使用所有这些功能。请享用!

添加JavaScript客户端

本快速入门将展示如何构建JavaScript客户端应用程序。用户将登录IdentityServer,使用IdentityServer发出的访问令牌调用Web API,并注销IdentityServer。

JavaScript客户端的新项目

为JavaScript应用程序创建一个新项目。它可以只是一个空的Web项目,也可以是一个空的ASP.NET Core应用程序。此快速入门将使用空的ASP.NET Core应用程序。

创建一个新的ASP.NET Core Web应用程序:

选择“空”模板:

单击“确定”按钮以创建项目。

修改托管(如此处所述)以在端口5003上运行。

添加静态文件中间件

鉴于该项目主要用于客户端,我们需要ASP.NET Core来提供构成我们应用程序的静态HTML和JavaScript文件。静态文件中间件旨在实现此目的。

在方法中注册Startup.cs中的静态文件中间件 Configure

public void Configure(IApplicationBuilder app)
{
  app.UseDefaultFiles();
  app.UseStaticFiles();
}

此中间件现在将从应用程序的〜/ wwwroot文件夹中提供静态文件。这是我们将放置HTML和JavaScript文件的地方。

参考OIDC客户端

在MVC项目中,我们使用库来处理OpenID Connect协议。在这个项目中,我们需要一个类似的库,除了一个在JavaScript中运行并且设计为在浏览器中运行的库。该OIDC客户端库就是这样一个图书馆。它可以通过NPM,Bower以及从github 直接下载。

如果要使用NPM下载oidc-client,请按照以下步骤操作:

将新的NPM包文件添加到项目中并将其命名为package.json:

在的package.json一个补充 dependency oidc-client

"dependencies": {
"oidc-client": "1.4.1"
}

保存此文件后,Visual Studio应自动将这些包还原到名为node_modules的文件夹中:

在〜/ node_modules / oidc-client / dist文件夹中找到名为oidc-client.js的文件,并将其复制到应用程序的〜/ wwwroot文件夹中。有更复杂的方法将NPM包复制到〜/ wwwroot,但这些技术超出了本快速入门的范围。

添加HTML和JavaScript文件

接下来是将您的HTML和JavaScript文件添加到〜/ wwwroot。我们将有两个HTML文件和一个特定于应用程序的JavaScript文件(除了oidc-client.js库)。在〜/ wwwroot中,添加一个名为index.html和callback.html的HTML文件,并添加一个名为app.js的JavaScript文件。

的index.html

这将是我们的应用程序中的主页。它将只包含用于登录,注销和调用Web API的按钮的HTML。它还将包含 <script> 标记以包含我们的两个JavaScript文件。它还包含 <pre> 用于向用户显示消息的用途。

它应该如下所示:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
</head>
<body>
  <button id="login">Login</button>
  <button id="api">Call API</button>
  <button id="logout">Logout</button>

  <pre id="results"></pre>

  <script src="oidc-client.js"></script>
  <script src="app.js"></script>
</body>
</html>

app.js

这将包含我们的应用程序的主要代码。第一件事是添加一个帮助函数来将消息记录到 <pre>

function log() {
  document.getElementById('results').innerText = '';

  Array.prototype.forEach.call(arguments, function (msg) {
      if (msg instanceof Error) {
          msg = "Error: " + msg.message;
      }
      else if (typeof msg !== '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连接协议。它需要MVC Client中必需的类似配置(尽管具有不同的值)。添加此代码以配置和实例化 UserManager

var config = {
  authority: "http://localhost:5000",
  client_id: "js",
  redirect_uri: "http://localhost:5003/callback.html",
  response_type: "id_token token",
  scope:"openid profile api1",
  post_logout_redirect_uri : "http://localhost:5003/index.html",
};
var mgr = new Oidc.UserManager(config);

接下来, UserManager 提供 getUser API以了解用户是否登录到JavaScript应用程序。它使用JavaScript Promise 以异步方式返回结果。返回的 User 对象具有 profile 包含用户声明的属性。添加此代码以检测用户是否已登录JavaScript应用程序:

mgr.getUser().then(function (user) {
  if (user) {
      log("User logged in", user.profile);
  }
  else {
      log("User not logged in");
  }
});

接下来,我们要实现的 login api logout 功能。在 UserManager 提供了 signinRedirect 登录用户,并且 signoutRedirect 以注销用户。 User 我们在上面的代码中获得的对象还具有 access_token 可用于通过Web API进行身份验证的属性。在 access_token 将被传递给通过网络API 授权与头承载方案。添加此代码以在我们的应用程序中实现这三个功能:

function login() {
  mgr.signinRedirect();
}

function api() {
  mgr.getUser().then(function (user) {
      var url = "http://localhost:5001/identity";

      var xhr = new XMLHttpRequest();
      xhr.open("GET", url);
      xhr.onload = function () {
          log(xhr.status, JSON.parse(xhr.responseText));
      }
      xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
      xhr.send();
  });
}

function logout() {
  mgr.signoutRedirect();
}

callback.html

redirect_uri 一旦用户登录IdentityServer,此HTML文件就是指定的页面。它将完成与IdentityServer的OpenID Connect协议登录握手。这个代码全部由 UserManager 我们之前使用的类提供。登录完成后,我们可以将用户重定向回主index.html页面。添加此代码以完成登录过程:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
</head>
<body>
  <script src="oidc-client.js"></script>
  <script>
      new Oidc.UserManager().signinRedirectCallback().then(function () {
          window.location = "index.html";
      }).catch(function (e) {
          console.error(e);
      });
  </script>
</body>
</html>

客户注册加入IdentityServer的JavaScript客户端

既然客户端应用程序已经准备就绪,我们需要在IdentityServer中为这个新的JavaScript客户端定义一个配置条目。在IdentityServer项目中找到客户端配置(在Config.cs中)。将新客户端添加到我们的新JavaScript应用程序的列表中。它应具有下面列出的配置:

// JavaScript Client
new Client
{
  ClientId = "js",
  ClientName = "JavaScript Client",
  AllowedGrantTypes = GrantTypes.Implicit,
  AllowAccessTokensViaBrowser = true,

  RedirectUris =           { "http://localhost:5003/callback.html" },
  PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
  AllowedCorsOrigins =     { "http://localhost:5003" },

  AllowedScopes =
  {
      IdentityServerConstants.StandardScopes.OpenId,
      IdentityServerConstants.StandardScopes.Profile,
      "api1"
  }
}

让Ajax调用与CORS网络API

最后一点配置是在Web API项目中配置CORS。这将允许从http:// localhost:5003到http:// localhost:5001进行Ajax调用。

配置CORS

ConfigureServices 在Startup.cs中将CORS服务添加到依赖注入系统:

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvcCore()
      .AddAuthorization()
      .AddJsonFormatters();

  services.AddAuthentication("Bearer")
      .AddIdentityServerAuthentication(options =>
      {
          options.Authority = "http://localhost:5000";
          options.RequireHttpsMetadata = false;

          options.ApiName = "api1";
      });

  services.AddCors(options =>
  {
      // this defines a CORS policy called "default"
      options.AddPolicy("default", policy =>
      {
          policy.WithOrigins("http://localhost:5003")
              .AllowAnyHeader()
              .AllowAnyMethod();
      });
  });
}

将CORS中间件添加到管道中 Configure

public void Configure(IApplicationBuilder app)
{
  app.UseCors("default");

  app.UseAuthentication();

  app.UseMvc();
}

运行JavaScript应用程序

现在您应该能够运行JavaScript客户端应用程序:

单击“登录”按钮以对用户进行签名。一旦用户返回到JavaScript应用程序,您应该看到他们的个人资料信息:

然后单击“API”按钮以调用Web API:

最后点击“退出”以签署用户。

您现在可以开始使用IdentityServer进行登录,注销和验证对Web API的调用的JavaScript客户端应用程序。

使用EntityFramework Core进行配置和操作数据

IdentityServer旨在实现可扩展性,其中一个可扩展点是用于IdentityServer所需数据的存储机制。本快速入门展示了如何配置IdentityServer以使用EntityFramework(EF)作为此数据的存储机制(而不是使用我们迄今为止使用的内存中实现)。

除了手动配置EF支持外,还有一个IdentityServer模板可用于创建具有EF支持的新项目。使用创建它。有关更多信息,请参见此处 dotnet new is4ef

IdentityServer4.EntityFramework

我们正在向数据库移动两种类型的数据。第一个是配置数据(资源和客户端)。第二个是IdentityServer在使用时产生的操作数据(令牌,代码和同意)。这些存储使用接口建模,我们在IdentityServer4.EntityFramework Nuget包中提供这些接口的EF实现。

通过添加IdentityServer项目的IdentityServer4.EntityFramework Nuget包的引用开始。

使用的SqlServer

鉴于EF的灵活性,您可以使用任何EF支持的数据库。对于本快速入门,我们将使用Visual Studio附带的SqlServer的LocalDb版本。

数据库架构更改和使用EF迁移

该IdentityServer4.EntityFramework包中包含从IdentityServer的模型映射实体类。作为IdentityServer的车型变化,所以会在实体类IdentityServer4.EntityFramework。当您使用IdentityServer4.EntityFramework并随着时间的推移升级时,您将负责自己的数据库架构以及实体类更改时该架构所需的更改。管理这些更改的一种方法是使用EF迁移,此快速入门将显示如何完成此操作。如果迁移不是您的首选项,那么您可以以任何您认为合适的方式管理架构更改。

为IdentityServer4.EntityFramework中的实体维护SqlServer的SQL脚本。他们就在这里。

用于迁移的EF工具

除了使用EF迁移跟踪架构更改之外,我们还将使用它在数据库中创建初始架构。这需要使用EF Core工具(此处有更多详细信息)。我们现在将添加它们,不幸的是,这必须通过手动编辑.csproj文件来完成。要通过右键单击项目来编辑.csproj,然后选择“编辑projectname.csproj”:

根据您为IdentityServer主机创建初始项目的方式,您可能已在csproj文件中配置了这些工具。如果是,您可以跳到下一部分。

然后在结尾</ Project>元素之前添加以下代码段:

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>

它应该看起来像这样:

保存并关闭文件。要测试您是否正确安装了这些工具,可以在与项目相同的目录中打开命令shell并运行dotnet ef。它应该如下所示:

接下来的步骤是,以取代当前呼叫 AddInMemoryClients AddInMemoryIdentityResources AddInMemoryApiResources ConfigureServices 在方法Startup.cs。我们将使用以下代码替换它们:

const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
  .AddDeveloperSigningCredential()
  .AddTestUsers(Config.GetUsers())
  // this adds the config data from DB (clients, resources)
  .AddConfigurationStore(options =>
  {
      options.ConfigureDbContext = builder =>
          builder.UseSqlServer(connectionString,
              sql => sql.MigrationsAssembly(migrationsAssembly));
  })
  // this adds the operational data from DB (codes, tokens, consents)
  .AddOperationalStore(options =>
  {
      options.ConfigureDbContext = builder =>
          builder.UseSqlServer(connectionString,
              sql => sql.MigrationsAssembly(migrationsAssembly));

      // this enables automatic token cleanup. this is optional.
      options.EnableTokenCleanup = true;
      options.TokenCleanupInterval = 30;
  });

您可能需要将这些命名空间添加到文件中:

using Microsoft.EntityFrameworkCore;
using System.Reflection;

上面的代码是对连接字符串进行硬编码,如果您愿意,可以随意更改。此外,调用 AddConfigurationStore AddOperationalStore 注册EF支持的商店实现。

传递给这些API的“构建器”回调函数是EF机制,允许您为这两个存储中的每一个配置 DbContextOptionsBuilder for DbContext 。这就是我们的 DbContext 类可以使用您要使用的数据库提供程序进行配置的方式。在这种情况下,通过调用 UseSqlServer 我们正在使用SqlServer。您也可以看出,这是提供连接字符串的位置。

“options”回调函数用于 UseSqlServer 配置定义EF迁移的程序集。EF需要使用迁移来定义数据库的模式。

托管应用程序负责定义这些迁移,因为它们特定于您的数据库和提供程序。

我们接下来会添加迁移。

要创建迁移,请在IdentityServer项目目录中打开命令提示符。在命令提示符下运行以下两个命令:

dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb

它应该看起来像这样:

您现在应该在项目中看到〜/ Data / Migrations / IdentityServer文件夹。其中包含新创建的迁移的代码。

如果您的数据库项目是一个单独的类库,并修复了错误“无法创建类型的对象”<您的名字> DbContext'。将“IDesignTimeDbContextFactory”的实现添加到项目中,或者参阅https://go.microsoft.com/fwlink/?linkid=851728以获取在设计时支持的其他模式。通过添加IDesignTimeDbContextFactory的实现,您还需要PersistedGrantDbContext和ConfigurationDbContext的工厂实现。

初始化数据库

现在我们已经进行了迁移,我们可以编写代码来从迁移中创建数据库。我们还将使用我们在之前的快速入门中定义的内存配置数据来为数据库设定种子。

在Startup.cs中添加此方法以帮助初始化数据库:

private void InitializeDatabase(IApplicationBuilder app)
{
  using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
  {
      serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();

      var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
      context.Database.Migrate();
      if (!context.Clients.Any())
      {
          foreach (var client in Config.GetClients())
          {
              context.Clients.Add(client.ToEntity());
          }
          context.SaveChanges();
      }

      if (!context.IdentityResources.Any())
      {
          foreach (var resource in Config.GetIdentityResources())
          {
              context.IdentityResources.Add(resource.ToEntity());
          }
          context.SaveChanges();
      }

      if (!context.ApiResources.Any())
      {
          foreach (var resource in Config.GetApiResources())
          {
              context.ApiResources.Add(resource.ToEntity());
          }
          context.SaveChanges();
      }
  }
}

然后我们可以从 Configure 方法中调用它:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  // this will do the initial DB population
  InitializeDatabase(app);

  // the rest of the code that was already here
  // ...
}

现在,如果运行IdentityServer项目,则应创建数据库并使用快速入门配置数据进行种子设定。您应该能够使用SQL Server Management Studio或Visual Studio来连接和检查数据。

上面的 InitializeDatabase 辅助API可以方便地为数据库设定种子,但是这种方法并不适合每次运行应用程序时执行。填充数据库后,请考虑删除对API的调用。

运行客户端应用程序

您现在应该能够运行任何现有的客户端应用程序并登录,获取令牌并调用API - 所有这些都基于数据库配置。

本节中的代码仍然依赖于Config.cs及其虚构用户Alice和Bob。如果您的用户列表很简短且静态,则调整后的Config.cs版本可能就足够了,但您可能希望在数据库中动态管理更大且更流畅的用户列表。ASP.NET Identity是一个需要考虑的选项,下一节的快速入门列出了此解决方案的示例实现。

社区快速入门和样本

IdentityServer组织不维护这些示例。IdentityServer组织愉快地链接到社区样本,但不能对样本做出任何保证。请直接与作者联系。

各种ASP.NET核心安全样本

https://github.com/leastprivilege/AspNetCoreSecuritySamples

IdentityServer4 EF和ASP.NET身份

此示例结合了EF和ASP.NET Identity快速入门(#6和#8)。

共同托管IdentityServer4和

此示例显示如何在与保护API的IdentityServer相同的主机中托管API。

https://github.com/brockallen/IdentityServerAndApi

MongoDB的IdentityServer4示例

IdentityServer4-mongo:与Quickstart#8 EntityFramework配置类似,但使用MongoDB配置数据。

IdentityServer4-mongo-AspIdentity:更详细的示例基于使用ASP.NET Identity进行身份管理,使用MongoDB作为配置数据

https://github.com/souzartn/IdentityServer4.Samples.Mongo

从Facebook,Google和Twitter交换外部令牌

显示如何使用扩展授权将外部身份验证令牌交换到身份服务器访问令牌

https://github.com/waqaskhan540/IdentityServerExternalAuth

IdentityServer4 Quickstart UI的ASP.NET Core MVC RazorPages模板

基于Razor Pages的QuickStart示例由Martin Fletcher提供。

.NET Core和ASP.NET Core“平台”场景

显示可信“内部”应用程序和“外部”应用程序与.NET Core 2.0和ASP.NET Core 2.0应用程序的交互

https://github.com/BenjaminAbt/Samples.AspNetCore-IdentityServer4

固定节点API与来自使用JWKS IdentityServer4令牌

演示如何使用IdentityServer4中的JWKS端点和RS256算法保护节点(Express)API。

使用更高质量的生产就绪模块提供IdentityServer4.Samples中NodeJsApi样本的替代方案。

https://github.com/lyphtec/idsvr4-node-jwks

IdentityServer是中间件和服务的组合。所有配置都在您的启动类中完成。

您可以通过调用以下方法将IdentityServer服务添加到DI系统:

public void ConfigureServices(IServiceCollection services)
{
  var builder = services.AddIdentityServer();
}

您可以选择将选项传入此调用。有关选项的详细信息,请参见此

这将返回一个构建器对象,该构建器对象又有许多方便的方法来连接其他服务。

AddSigningCredential

添加签名密钥服务,该服务为各种令牌创建/验证服务提供指定的密钥材料。您可以从证书存储中传入证书的a X509Certificate2 ,a SigningCredential 或引用。

AddValidationKey

添加用于验证令牌的密钥。它们将由内部令牌验证器使用,并将显示在发现文档中。您可以从证书存储中传入证书的a X509Certificate2 ,a SigningCredential 或引用。这对于关键翻转场景非常有用。

内存配置存储

各种“内存中”配置API允许从内存中的配置对象列表配置IdentityServer。这些“内存中”集合可以在宿主应用程序中进行硬编码,也可以从配置文件或数据库动态加载。但是,通过设计,这些集合仅在托管应用程序启动时创建。

使用这些配置API的目的是在原型设计,开发和/或测试时使用,在这种情况下,不需要在运行时为配置数据动态查询数据库。如果配置很少更改,则此配置样式也可能适用于生产方案,或者如果必须更改值,则要求重新启动应用程序并不方便。

AddInMemoryClients

基于配置对象的内存中集合的注册 IClientStore ICorsPolicyService 实现 Client

TestUser 级车型的用户,他们的凭据,并在IdentityServer索赔。使用 TestUser “内存”商店是因为它适用于原型设计,开发和/或测试。采用 TestUser 在生产中不推荐使用。

AddTestUsers

TestUserStore 基于 TestUser 对象集合的注册。 TestUserStore 由默认的快速入门UI使用。还注册 IProfileService 和的实现 IResourceOwnerPasswordValidator

AddExtensionGrantValidator

添加 IExtensionGrantValidator 实现以用于扩展授权。

AddCorsPolicyCache

注册 ICorsPolicyService 装饰器实现,该实现将维护CORS策略服务评估结果的内存缓存。缓存持续时间可在 Caching 配置选项上配置 IdentityServerOptions

可以进一步自定义缓存:

默认缓存依赖于 ICache<T> 实现。如果要自定义特定配置对象的缓存行为,可以在依赖项注入系统中替换此实现。

ICache<T> 本身的默认实现依赖于.NET提供的 IMemoryCache 接口(和 MemoryCache 实现)。如果要自定义内存中缓存行为,可以替换 IMemoryCache 依赖项注入系统中的实现。

您需要通过调用以下方法将IdentityServer添加到管道:

public void Configure(IApplicationBuilder app)
{
  app.UseIdentityServer();
}

UseIdentityServer 包括打电话 UseAuthentication ,因此没有必要同时使用。

中间件没有其他配置。

请注意,订单在管道中很重要。例如,您需要在实现登录屏幕的UI框架之前添加IdentitySever。

您通常在系统中定义的第一件事是您要保护的资源。这可能是您的用户的身份信息,如个人资料数据或电子邮件地址,或访问API。

您可以使用C#对象模型定义资源 - 或从数据存储加载它们。的实施 IResourceStore 与这些低级别的细节交易。对于本文档,我们使用内存中实现。

定义身份资源

身份资源是用户的用户ID,名称或电子邮件地址等数据。标识资源具有唯一名称,您可以为其分配任意声明类型。然后,这些声明将包含在用户的身份令牌中。客户端将使用该 scope 参数来请求访问标识资源。

OpenID Connect规范指定了几个标准身份资源。最低要求是,您为用户发送唯一ID提供支持 - 也称为主题ID。这是通过公开名为的标准身份资源来完成的 openid

public static IEnumerable<IdentityResource> GetIdentityResources()
{
  return new List<IdentityResource>
  {
      new IdentityResources.OpenId()
  };
}

该IdentityResources类支持的规范(OpenID的,电子邮件,个人资料,电话和地址)中定义的所有范围。如果您想全部支持它们,可以将它们添加到支持的身份资源列表中:

public static IEnumerable<IdentityResource> GetIdentityResources()
{
  return new List<IdentityResource>
  {
      new IdentityResources.OpenId(),
      new IdentityResources.Email(),
      new IdentityResources.Profile(),
      new IdentityResources.Phone(),
      new IdentityResources.Address()
  };
}

定义自定义标识资源

您还可以定义自定义标识资源。创建一个新的IdentityResource类,为其命名,并可选择显示名称和描述,并定义在请求此资源时应将哪些用户声明包含在身份令牌中:

public static IEnumerable<IdentityResource> GetIdentityResources()
{
  var customProfile = new IdentityResource(
      name: "custom.profile",
      displayName: "Custom profile",
      claimTypes: new[] { "name", "email", "status" });

  return new List<IdentityResource>
  {
      new IdentityResources.OpenId(),
      new IdentityResources.Profile(),
      customProfile
  };
}

有关身份资源设置的更多信息,请参阅参考部分。

定义API资源

要允许客户端请求API的访问令牌,您需要定义API资源,例如:

要获取API的访问权限,您还需要将它们注册为范围。这次范围类型是Resource类型:

public static IEnumerable<ApiResource> GetApis()
{
  return new[]
  {
      // simple API with a single scope (in this case the scope name is the same as the api name)
      new ApiResource("api1", "Some API 1"),

      // expanded version if more control is needed
      new ApiResource
      {
          Name = "api2",

          // secret for using introspection endpoint
          ApiSecrets =
          {
              new Secret("secret".Sha256())
          },

          // include the following using claims in access token (in addition to subject id)
          UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email },

          // this API defines two scopes
          Scopes =
          {
              new Scope()
              {
                  Name = "api2.full_access",
                  DisplayName = "Full access to API 2",
              },
              new Scope
              {
                  Name = "api2.read_only",
                  DisplayName = "Read only access to API 2"
              }
          }
      }
  };
}

有关API资源设置的更多信息,请参阅参考部分。

由资源定义的用户声明由IProfileService扩展点加载。

定义客户端

客户端表示可以从您的身份服务器请求令牌的应用程序。

详细信息各不相同,但您通常会为客户端定义以下常用设置:

唯一的客户ID

如果需要的秘密

允许与令牌服务的交互(称为授权类型)

发送身份和/或访问令牌的网络位置(称为重定向URI)

允许客户端访问的范围列表(也称为资源)

在运行时,通过实现来检索客户端 IClientStore 。这允许从任意数据源(如配置文件或数据库)加载它们。对于本文档,我们将使用客户端存储的内存版本。您可以 ConfigureServices 通过 AddInMemoryClients extensions方法连接内存存储。

定义服务器到服务器通信的客户端

在这种情况下,没有交互式用户 - 服务(也称为客户端)想要与API(aka范围)进行通信:

public class Clients
{
  public static IEnumerable<Client> Get()
  {
      return new List<Client>
      {
          new Client
          {
              ClientId = "service.client",
              ClientSecrets = { new Secret("secret".Sha256()) },

              AllowedGrantTypes = GrantTypes.ClientCredentials,
              AllowedScopes = { "api1", "api2.read_only" }
          }
      };
  }
}

定义基于浏览器的JavaScript客户端(例如SPA)以进行用户身份验证和委派访问以及

此客户端使用所谓的隐式流来从JavaScript请求身份和访问令牌:

var jsClient = new Client
{
  ClientId = "js",
  ClientName = "JavaScript Client",
  ClientUri = "http://identityserver.io",

  AllowedGrantTypes = GrantTypes.Implicit,
  AllowAccessTokensViaBrowser = true,

  RedirectUris =           { "http://localhost:7017/index.html" },
  PostLogoutRedirectUris = { "http://localhost:7017/index.html" },
  AllowedCorsOrigins =     { "http://localhost:7017" },

  AllowedScopes =
  {
      IdentityServerConstants.StandardScopes.OpenId,
      IdentityServerConstants.StandardScopes.Profile,
      IdentityServerConstants.StandardScopes.Email,

      "api1", "api2.read_only"
  }
};

定义服务器端Web应用程序(例如MVC)以进行使用身份验证和委托API访问

交互式服务器端(或本机桌面/移动)应用程序使用混合流。此流程为您提供最佳安全性,因为访问令牌仅通过反向通道调用传输(并允许您访问刷新令牌):

var mvcClient = new Client
{
  ClientId = "mvc",
  ClientName = "MVC Client",
  ClientUri = "http://identityserver.io",

  AllowedGrantTypes = GrantTypes.Hybrid,
  AllowOfflineAccess = true,
  ClientSecrets = { new Secret("secret".Sha256()) },

  RedirectUris =           { "http://localhost:21402/signin-oidc" },
  PostLogoutRedirectUris = { "http://localhost:21402/" },
  FrontChannelLogoutUri = "http://localhost:21402/signout-oidc",

  AllowedScopes =
  {
      IdentityServerConstants.StandardScopes.OpenId,
      IdentityServerConstants.StandardScopes.Profile,
      IdentityServerConstants.StandardScopes.Email,

      "api1", "api2.read_only"
  },
};

为了使IdentityServer能够代表用户发出令牌,该用户必须登录IdentityServer。

Cookie认证

使用由ASP.NET Core中的cookie身份验证处理程序管理的cookie来跟踪身份验证。

IdentityServer注册了两个cookie处理程序(一个用于身份验证会话,另一个用于临时外部cookie)。默认情况下使用它们,如果要手动引用它们,可以从 IdentityServerConstants 类( DefaultCookieAuthenticationScheme ExternalCookieAuthenticationScheme )中获取它们的名称。

我们只公开这些cookie的基本设置(到期和滑动),如果您需要更多控制,您可以注册自己的cookie处理程序。IdentityServer使用与使用ASP.NET Core 时 DefaultAuthenticateScheme 配置的cookie处理程序相匹配的cookie处理程序。 AuthenticationOptions``AddAuthentication

覆盖cookie处理程序配置

如果您希望使用自己的cookie身份验证处理程序,则必须自己配置它。这必须 ConfigureServices 在DI(with AddIdentityServer )中注册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不为用户身份验证提供任何用户界面或用户数据库。这些是您希望自己提供或发展的东西。

如果您需要基本UI的起点(登录,注销,同意和管理授权),您可以使用我们的快速入门UI。

快速入门UI针对内存数据库对用户进行身份验证。您可以通过访问真实用户存储来替换这些位。我们有使用ASP.NET Identity的示例。

登录工作流程

当IdentityServer在授权端点收到请求且未对用户进行身份验证时,将将用户重定向到已配置的登录页面。您必须通过选项上的 UserInteraction 设置(默认为)通知IdentityServer登录页面的路径。将传递一个参数,通知您的登录页面,登录完成后应重定向用户。 /account/login``returnUrl

通过参数注意开放重定向攻击 returnUrl 。您应该验证 returnUrl 引用的是众所周知的位置。请参阅API 的交互服务以验证 returnUrl 参数。

登录上下文

在您的登录页面上,您可能需要有关请求上下文的信息,以便自定义登录体验(例如客户端,提示参数,IdP提示或其他内容)。这可以通过交互服务 GetAuthorizationContextAsync 上的API获得。

发行cookie和声明

HttpContext ASP.NET Core 上有与身份验证相关的扩展方法,用于发出身份验证cookie并对用户进行签名。使用的身份验证方案必须与您正在使用的cookie处理程序匹配(请参见上文)。

当您签署用户时,您必须至少发出 sub 索赔和 name 索赔。IdentityServer还提供了一些 SignInAsync 扩展方法 HttpContext ,使其更加方便。

您还可以选择发出 idp 声明(针对身份提供者名称), amr 声明(针对所使用的身份验证方法)和/或 auth_time 声明(针对用户身份验证的纪元时间)。如果您不提供这些,则IdentityServer将提供默认值。

使用外部身份提供商登录

ASP.NET Core有一种灵活的方式来处理外部身份验证。这涉及几个步骤。

如果您使用的是ASP.NET标识,则会隐藏许多基础技术细节。建议您还阅读Microsoft 文档并执行ASP.NET Identity 快速入门。

为外部提供者添加身份验证处理程序

与外部提供者通信所需的协议实现封装在 身份验证处理程序中 。一些提供商使用专有协议(例如Facebook等社交提供商),有些提供商使用标准协议,例如OpenID Connect,WS-Federation或SAML2p。

有关添加外部身份验证和配置它的分步说明,请参阅此快速入门。

cookies的作用

调用外部身份验证处理程序的一个选项 SignInScheme ,例如:

services.AddAuthentication()
  .AddGoogle("Google", options =>
  {
      options.SignInScheme = "scheme of cookie handler to use";

      options.ClientId = "...";
      options.ClientSecret = "...";
  })

登录方案指定将临时存储外部认证结果的cookie处理程序的名称,例如由外部提供者发送的声明。这是必要的,因为在完成外部身份验证过程之前通常会涉及一些重定向。

鉴于这是一种常见做法,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 = "...";
  })

对于特殊情况,您还可以将外部cookie机制短路并将外部用户直接转发到主cookie处理程序。这通常涉及处理外部处理程序上的事件,以确保您从外部标识源执行正确的声明转换。

触发认证处理程序

您可以通过(或使用MVC ) ChallengeAsync 上的扩展方法调用外部认证处理程序。 HttpContext``ChallengeResult

您通常希望将一些选项传递给挑战操作,例如回调页面的路径和簿记提供者的名称,例如:

var callbackUrl = Url.Action("ExternalLoginCallback");

var props = new AuthenticationProperties
{
  RedirectUri = callbackUrl,
  Items =
  {
      { "scheme", provider },
      { "returnUrl", returnUrl }
  }
};

return Challenge(provider, props);

处理回调并签署用户

在回调页面上,您的典型任务是:

检查外部提供商返回的身份。

决定如何处理该用户。如果这是新用户或返回用户,则可能会有所不同。

新用户在被允许之前可能需要额外的步骤和UI。

可能会创建一个链接到外部提供程序的新内部用户帐户。

存储您要保留的外部声明。

删除临时cookie

检查外部身份

// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
  throw new Exception("External authentication error");
}

// retrieve claims of the external user
var externalUser = result.Principal;
if (externalUser == null)
{
  throw new Exception("External authentication error");
}

// retrieve claims of the external user
var claims = externalUser.Claims.ToList();

// try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
if (userIdClaim == null)
{
  userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
}
if (userIdClaim == null)
{
  throw new Exception("Unknown userid");
}

var externalUserId = userIdClaim.Value;
var externalProvider = userIdClaim.Issuer;

// use externalProvider and externalUserId to find your user, or provision a new user

清理和登录

// issue authentication cookie for user
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray());

// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);

// validate return URL and redirect back to authorization endpoint or a local page
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
  return Redirect(returnUrl);
}

return Redirect("~/");

国家,URL长度和ISecureDataFormat

重定向到外部提供程序以进行登录时,必须经常从客户端应用程序进行往返状态。这意味着在离开客户端之前捕获状态并保留状态,直到用户返回到客户端应用程序。许多协议(包括OpenID Connect)允许将某种状态作为参数传递作为请求的一部分,并且身份提供者将在响应上返回该状态。ASP.NET Core提供的OpenID Connect身份验证处理程序利用了协议的这一功能,这就是它实现上述 returnUrl 功能的方式。

在请求参数中存储状态的问题是请求URL可能变得太大(超过2000个字符的公共限制)。OpenID Connect身份验证处理程序确实提供了一个可扩展点,用于在服务器中而不是在请求URL中存储状态。您可以通过 ISecureDataFormat<AuthenticationProperties> 在OpenIdConnectOptions上实现和配置它来自行实现。

幸运的是,IdentityServer为您提供了一个实现,由 IDistributedCache DI容器中注册的实现(例如标准 MemoryDistributedCache )支持。要使用IdentityServer提供的安全数据格式实现,只需在配置DI时调用 AddOidcStateDataFormatterCache 扩展方法 IServiceCollection 。如果未传递任何参数,则配置的所有OpenID Connect处理程序将使用IdentityServer提供的安全数据格式实现:

public void ConfigureServices(IServiceCollection services)
{
  // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.
  services.AddOidcStateDataFormatterCache();

  services.AddAuthentication()
      .AddOpenIdConnect("demoidsrv", "IdentityServer", options =>
      {
          // ...
      })
      .AddOpenIdConnect("aad", "Azure AD", options =>
      {
          // ...
      })
      .AddOpenIdConnect("adfs", "ADFS", options =>
      {
          // ...
      });
}

如果只配置特定方案,则将这些方案作为参数传递:

public void ConfigureServices(IServiceCollection services)
{
  // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.
  services.AddOidcStateDataFormatterCache("aad", "demoidsrv");

  services.AddAuthentication()
      .AddOpenIdConnect("demoidsrv", "IdentityServer", options =>
      {
          // ...
      })
      .AddOpenIdConnect("aad", "Azure AD", options =>
      {
          // ...
      })
      .AddOpenIdConnect("adfs", "ADFS", options =>
      {
          // ...
      });
}
Windows身份验证

在支持的平台上,您可以使用IdentityServer对使用Windows身份验证的用户进行身份验证(例如,针对Active Directory)。当前使用以下命令托管IdentityServer时,Windows身份验证可用:

使用IIS和IIS集成包在Windows上使用Kestrel

Windows上的HTTP.sys服务器

在这两种情况下,使用该方案的 ChallengeAsync API 都会触发Windows身份验证。我们的快速入门UI中的帐户控制器实现了必要的逻辑。 HttpContext``"Windows"

使用Kestrel时,必须运行“后面”IIS并使用IIS集成:

var host = new WebHostBuilder()
  .UseKestrel()
  .UseUrls("http://localhost:5000")
  .UseContentRoot(Directory.GetCurrentDirectory())
  .UseIISIntegration()
  .UseStartup<Startup>()
  .Build();

使用该 WebHost.CreateDefaultBuilder 方法设置时,会自动配置红隼 WebHostBuilder

IIS(或IIS Express)中的虚拟目录也必须启用Windows并启用匿名身份验证。

IIS集成层将Windows身份验证处理程序配置为DI,可以通过身份验证服务调用。通常在IdentityServer中,建议禁用此自动行为。这是在 ConfigureServices

services.Configure<IISOptions>(iis =>
{
  iis.AuthenticationDisplayName = "Windows";
  iis.AutomaticAuthentication = false;
});

默认情况下,显示名称为空,Windows身份验证按钮不会显示在快速入门UI中。如果依赖于自动发现外部提供程序,则需要设置显示名称。

注销IdentityServer就像删除身份验证cookie一样简单,但是为了完成联合注销,我们必须考虑将用户从客户端应用程序(甚至可能是上游身份提供商)中签名。

要删除身份验证cookie,只需使用 SignOutAsync 扩展方法即可 HttpContext 。您将需要传递使用的方案( IdentityServerConstants.DefaultCookieAuthenticationScheme 除非您已更改,否则提供此方案):

await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);

或者您可以使用IdentityServer提供的便捷扩展方法:

await HttpContext.SignOutAsync();

通常,您应该提示用户注销(意味着需要POST),否则攻击者可能会链接到您的注销页面,导致用户自动注销。

通知客户端用户已注销

作为退出流程的一部分,您需要确保客户端应用程序被告知用户已退出。IdentityServer支持服务器端客户端的前端通道规范(例如MVC),服务器端客户端的反向通道 规范(例如MVC),以及基于浏览器的JavaScript客户端的会话管理规范(例如SPA,React,Angular)等)。

前端服务器端客户端

要通过前端通道规范从服务器端客户端应用程序注销用户,IdentityServer中的“已注销”页面必须呈现 <iframe> 以通知客户端用户已注销。希望收到通知的客户端必须 FrontChannelLogoutUri 设置配置值。IdentityServer跟踪用户已登录的客户端,并提供 GetLogoutContextAsync IIdentityServerInteractionService (详细信息)上调用的API 。此API返回一个 LogoutRequest 对象,该对象具有 SignOutIFrameUrl 您已注销的页面必须呈现为的属性 <iframe>

反向通道服务器端客户端

要通过反向通道规范从服务器端客户端应用程序注销用户 SignOutIFrameUrl ,IdentityServer中的端点将自动触发服务器到服务器调用,将签名的注销请求传递给客户端。这意味着即使如果没有前面通道的客户端中,“退出”,在IdentityServer页仍必须渲染 <iframe> SignOutIFrameUrl 如上所述。希望收到通知的客户端必须 BackChannelLogoutUri 设置配置值。

基于浏览器的JavaScript客户端

鉴于会话管理规范的设计方式,IdentityServer中没有什么特别的,您需要做的是通知这些客户端用户已注销。但是,客户端必须对check_session_iframe执行监视,这是由oidc-client JavaScript库实现的。

由客户端应用程序启动的注销

如果客户端应用程序启动了注销,则客户端首先将用户重定向到结束会话端点。在结束会话端点处的处理可能需要通过重定向到注销页面来维护一些临时状态(例如,客户端的注销后重定向uri)。此状态可能对注销页面有用,并且状态的标识符通过logoutId参数传递到注销页面。

GetLogoutContextAsync 上的API 交互服务可以用来加载状态。感兴趣的 ShowSignoutPrompt ShowSignoutPrompt 指示注销请求是否已经过身份验证,因此不会提示用户注销是安全的。

默认情况下,此状态作为通过logoutId值传递的受保护数据结构进行管理。如果您希望在结束会话端点和注销页面之间使用其他一些持久性,那么您可以 IMessageStore<LogoutMessage> 在DI中实现并注册实现。

退出外部身份提供商

当用户注销 IdentityServer,并且他们使用外部身份提供程序登录时,可能会将其重定向到也注销外部提供程序。并非所有外部提供商都支持注销,因为它取决于它们支持的协议和功能。

要检测是否必须将用户重定向到外部身份提供程序以进行注销通常是通过使用 idp 在IdentityServer中发布到cookie中的声明来完成的。设置到此声明中的值是 AuthenticationScheme 相应的身份验证中间件。在签出时,咨询此索赔以了解是否需要外部签出。

由于正常注销工作流程已经需要清理和状态管理,因此将用户重定向到外部身份提供商是有问题的。然后,在IdentityServer完成正常注销和清理过程的唯一方法是从外部身份提供程序请求在注销后将用户重定向回IdentityServer。并非所有外部提供商都支持退出后重定向,因为它取决于它们支持的协议和功能。

然后,签出时的工作流程将撤消IdentityServer的身份验证cookie,然后重定向到请求退出后重定向的外部提供程序。退出后重定向应保持此处描述的必要签出状态(即 logoutId 参数值)。要在外部提供程序注销后重定向回IdentityServer, RedirectUri 应该 AuthenticationProperties 在使用ASP.NET Core的 SignOutAsync API 时使用,例如:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
  // build a model so the logged out page knows what to display
  var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId);

  var user = HttpContext.User;
  if (user?.Identity.IsAuthenticated == true)
  {
      // delete local authentication cookie
      await HttpContext.SignOutAsync();

      // raise the logout event
      await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetName()));
  }

  // check if we need to trigger sign-out at an upstream identity provider
  if (vm.TriggerExternalSignout)
  {
      // build a return URL so the upstream provider will redirect back
      // to us after the user has logged out. this allows us to then
      // complete our single sign-out processing.
      string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

      // this triggers a redirect to the external provider for sign-out
      return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
  }

  return View("LoggedOut", vm);
}

一旦用户退出外部提供程序然后重定向回来,IdentityServer的正常注销处理应该执行,这涉及处理 logoutId 和执行所有必要的清理。

联合注销是指用户使用外部身份提供程序登录IdentityServer,然后用户通过IdentityServer未知的工作流程注销该外部身份提供程序的情况。当用户注销时,对IdentityServer进行通知将非常有用,这样它就可以将用户从IdentityServer和使用IdentityServer的所有应用程序签名。

并非所有外部身份提供商都支持联合注销,但那些提供的机制将提供通知客户端用户已注销的机制。此通知通常以 <iframe> 来自外部身份提供商的“已注销”页面的请求的形式出现。然后IdentityServer必须通知其所有客户(如讨论这里),也通常在一个请求的形式 <iframe> 从内外部身份提供的 <iframe>

使联合注销成为特殊情况(与正常注销相比)的原因是联合注销请求不是IdentityServer中的正常注销端点。实际上,每个外部IdentityProvider都将在IdentityServer主机中具有不同的端点。这是因为每个外部身份提供者可能使用不同的协议,并且每个中间件都在不同的端点上进行侦听。

所有这些因素的净效果是没有像正常注销工作流那样呈现“注销”页面,这意味着我们缺少对IdentityServer客户端的注销通知。我们必须为每个联合注销端点添加代码,以呈现必要的通知以实现联合注销。

幸运的是,IdentityServer已经包含此代码。当请求进入IdentityServer和调用外部认证提供商处理,IdentityServer检测,如果这些联合signout要求,如果他们是它会自动呈现相同 <iframe> 的这里描述signout。简而言之,自动支持联合注销。

通用架构是所谓的联合网关。在这种方法中,IdentityServer充当一个或多个外部身份提供者的网关。

该架构具有以下优点

您的应用程序只需要了解一个令牌服务(网关),并且不受有关连接到外部提供程序的所有详细信息的影响。这也意味着您可以添加或更改这些外部提供程序,而无需更新您的应用程序。

您控制网关(而不是某些外部服务提供商) - 这意味着您可以对其进行任何更改,并保护您的应用程序免受外部提供程序可能对其自己的服务所做的更改。

大多数外部提供商仅支持一组固定的声明和声明类型 - 在中间具有网关允许对提供商的响应进行后处理以转换/添加/修改特定于域的身份信息。

某些提供商不支持访问令牌(例如社交提供商) - 因为网关知道您的API,它可以根据外部身份发出访问令牌。

某些提供商按您连接的应用程序数收费。网关充当外部提供程序的单个应用程序。在内部,您可以根据需要连接任意数量的应用程序。

一些提供商使用专有协议或对标准协议进行专有修改 - 通过网关,您只需要处理一个地方。

强制每个身份验证(内部或外部)通过一个地方为身份映射提供极大的灵活性,为您的所有应用程序提供稳定的身份并处理新的需求

换句话说 - 拥有联合网关可以让您对身份基础架构进行大量控制。由于您的用户身份是您最重要的资产之一,我们建议您控制网关。

我们的快速入门UI使用了以下一些功能。另请参阅外部身份验证快速入门和有关外部提供程序的文档。

您可以通过向IdentityServer应用程序添加身份验证处理程序来添加对外部身份提供程序的支持。

你可以通过调用程序查询这些外部供应商 IAuthenticationSchemeProvider 。这允许基于已注册的外部提供程序动态呈现您的登录页面。

我们的客户端配置模型允许基于每个客户端限制可用的提供者(使用该 IdentityProviderRestrictions 属性)。

您还可以使用 EnableLocalLogin 客户端上的属性告诉您的UI是否应该呈现用户名/密码输入。

我们的快速启动UI漏斗通过一个回调所有外部认证调用(见 ExternalLoginCallback AccountController 类)。这允许单点进行后处理。

在授权请求期间,如果IdentityServer需要用户同意,则浏览器将被重定向到同意页面。

同意用于允许最终用户授予客户端对资源(身份或API)的访问权限。这通常仅对第三方客户端是必需的,并且可以在客户端设置上按客户端启用/禁用。

为了让用户同意,托管应用程序必须提供同意页面。该快速入门UI有一个批准页面的基本实现。

同意页面通常呈现当前用户的显示名称,请求访问的客户端的显示名称,客户端的徽标,有关客户端的更多信息的链接以及客户端请求访问的资源列表。允许用户表明他们的同意应该被“记住”也是很常见的,因此将来不会再次提示同一客户。

一旦用户提供了同意,同意页面必须通知IdentityServer同意,然后必须将浏览器重定向回授权端点。

授权上下文

IdentityServer将returnUrl参数(可在用户交互选项上配置)传递到包含授权请求参数的同意页面。这些参数提供了同意页面的上下文,可以在交互服务的帮助下阅读。该 GetAuthorizationContextAsync API将返回的实例 AuthorizationRequest

可以使用 IClientStore IResourceStore 接口获取有关客户端或资源的其他详细信息。

通知IdentityServer同意结果

GrantConsentAsync 对API 的交互服务允许同意页面知情同意的结果(这也可能拒绝客户端访问)的IdentityServer。

IdentityServer将暂时保留同意的结果。这种持久性默认使用cookie,因为它只需要持续足够长的时间来将结果传回给授权端点。这种临时持久性与用于“记住我的同意”功能的持久性不同(并且授权端点持续“记住我对用户的同意”)。如果您希望在同意页面和授权重定向之间使用其他一些持久性,那么您可以 IMessageStore<ConsentResponse> 在DI中实现并注册实现。

将用户返回到授权端点

一旦同意页面通知IdentityServer结果,就可以将用户重定向回returnUrl。您的同意页面应通过验证returnUrl是否有效来防止打开重定向。这可以通过调用来完成 IsValidReturnUrl 的交互服务。此外,如果 GetAuthorizationContextAsync 返回非null结果,那么您还可以信任returnUrl有效。

IdentityServer 默认以JWT(JSON Web令牌)格式发出访问令牌。

今天的每个相关平台都支持验证JWT令牌,这里可以找到一个很好的JWT库列表。热门图书馆例如:

ASP.NET Core的JWT承载认证处理程序

Katana的JWT承载认证中间件

Katana的IdentityServer身份验证中间件

jsonwebtoken为的NodeJS

保护基于ASP.NET核心的API只需在DI中配置JWT承载认证处理程序,并将认证中间件添加到管道:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
      services.AddMvc();

      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
          .AddJwtBearer(options =>
          {
              // base-address of your identityserver
              options.Authority = "https://demo.identityserver.io";

              // name of the API resource
              options.Audience = "api1";
          });
  }

  public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
  {
      app.UseAuthentication();
      app.UseMvc();
  }
}

IdentityServer身份验证处理程序

我们的身份验证处理程序与上面的处理程序具有相同的用途(实际上它在内部使用Microsoft JWT库),但添加了一些其他功能:

支持JWT和参考令牌

用于引用标记的可扩展缓存

统一配置模型

对于最简单的情况,我们的处理程序配置看起来非常类似于上面的代码段:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
      services.AddMvc();

      services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
          .AddIdentityServerAuthentication(options =>
          {
              // base-address of your identityserver
              options.Authority = "https://demo.identityserver.io";

              // name of the API resource
              options.ApiName = "api1";
          });
  }

  public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
  {
      app.UseAuthentication();
      app.UseMvc();
  }
}

你可以从nuget 或github获得包。

支持引用标记

如果传入令牌不是JWT,我们的中间件将联系发现文档中的内省端点以验证令牌。由于内省端点需要身份验证,因此您需要提供已配置的API密钥,例如:

.AddIdentityServerAuthentication(options =>
{
  // base-address of your identityserver
  options.Authority = "https://demo.identityserver.io";

  // name of the API resource
  options.ApiName = "api1";
  options.ApiSecret = "secret";
})

通常,您不希望为每个传入请求执行到内省端点的往返。中间件有一个内置缓存,您可以像这样启用:

.AddIdentityServerAuthentication(options =>
{
  // base-address of your identityserver
  options.Authority = "https://demo.identityserver.io";

  // name of the API resource
  options.ApiName = "api1";
  options.ApiSecret = "secret";

  options.EnableCaching = true;
  options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default
})

处理程序将使用在DI容器中注册的任何IDistributedCache实现(例如标准的MemoryDistributedCache)。

所述ApiName属性检查该令牌具有匹配观众(或短 aud ),如权利要求。

在IdentityServer中,您还可以将API细分为多个范围。如果需要该粒度,可以使用ASP.NET Core授权策略系统来检查范围。

制定全球政策

services
  .AddMvcCore(options =>
  {
      // require scope1 or scope2
      var policy = ScopePolicy.Create("scope1", "scope2");
      options.Filters.Add(new AuthorizeFilter(policy));
  })
  .AddJsonFormatters()
  .AddAuthorization();

制定范围政策

services.AddAuthorization(options =>
{
  options.AddPolicy("myPolicy", builder =>
  {
      // require scope1
      builder.RequireScope("scope1");
      // and require scope2 or scope3
      builder.RequireScope("scope2", "scope3");
  });
});

您的身份服务器只是一个标准的ASP.NET核心应用程序,包括IdentityServer中间件。首先阅读有关发布和部署的官方Microsoft 文档。

通常,您将设计IdentityServer部署以实现高可用性:

IdentityServer本身是无状态的,不需要服务器关联 - 但是有些数据需要在实例之间共享。

这通常包括:

启动配置,例如密钥材料,外部提供商设置等......

存储数据的方式取决于您的环境。在配置数据很少更改的情况下,我们建议使用内存存储和代码或配置文件。

在高度动态的环境(例如Saas)中,我们建议使用数据库或配置服务动态加载配置。

IdentityServer支持开箱即用的代码配置和配置文件(请参阅此处)。对于数据库,我们为基于Entity Framework Core的数据库提供支持。

您还可以通过实现 IResourceStore 和构建自己的配置存储 IClientStore

另一个重要的启动配置是您的主要材料,请参阅此处以获取有关密钥材料和加密的更多详细信息。

对于某些操作,IdentityServer需要持久性存储来保持状态,这包括:

发布授权码

发出引用和刷新令牌

您可以使用传统数据库存储操作数据,也可以使用具有Redis等持久性功能的缓存。上面提到的EF Core实现也支持运营数据。

您还可以通过实现实现对自己的自定义存储机制的支持 IPersistedGrantStore - 默认情况下IdentityServer会注入内存中的版本。

ASP.NET核心数据保护

ASP.NET Core本身需要共享密钥材料来保护cookie,状态字符串等敏感数据。请参阅此处的官方文档。

您可以重复使用上述持久性存储之一,也可以使用像共享文件这样的简单文件。

IdentityServer使用ASP.NET Core提供的标准日志记录工具。Microsoft 文档有一个很好的介绍和内置日志记录提供程序的说明。

我们大致遵循Microsoft使用日志级别的指导原则:

Trace 仅供开发人员解决问题的信息。这些消息可能包含敏感的应用程序数据(如令牌),不应在生产环境中启用。

Debug 遵循内部流程并理解为何做出某些决定。在开发和调试过程中具有短期实用性。

Information 用于跟踪应用程序的一般流程。这些日志通常具有一些长期价值。

Warning 对于应用程序流中的异常或意外事件。这些可能包括错误或其他不会导致应用程序停止但可能需要调查的条件。

Error 对于无法处理的错误和异常。示例:协议请求的验证失败。

Critical 对于需要立即关注的故障。示例:缺少商店实施,无效的密钥材料......

Serilog的设置

我们个人非常喜欢Serilog。试试看。

ASP.NET Core2.0+

对于以下配置,您需要 Serilog.AspNetCore Serilog.Sinks.Console 包:

public class Program
{
  public static void Main(string[] args)
  {
      Console.Title = "IdentityServer4";

      Log.Logger = new LoggerConfiguration()
          .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();
  }

  public static IWebHost BuildWebHost(string[] args)
  {
      return WebHost.CreateDefaultBuilder(args)
              .UseStartup<Startup>()
              .UseSerilog()
              .Build();
  }
}

对于以下配置,您需要 Serilog.Extensions.Logging Serilog.Sinks.Console 包:

public class Program
{
  public static void Main(string[] args)
  {
      Console.Title = "IdentityServer4";

      Log.Logger = new LoggerConfiguration()
          .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();
  }

  public static IWebHost BuildWebHost(string[] args)
  {
      return WebHost.CreateDefaultBuilder(args)
              .UseStartup<Startup>()
              .ConfigureLogging(builder =>
              {
                  builder.ClearProviders();
                  builder.AddSerilog();
              })
              .Build();
  }
}

日志记录是更低级别的“printf”样式 - 事件代表有关IdentityServer中某些操作的更高级别信息。事件是结构化数据,包括事件ID,成功/失败信息,类别和详细信息。这使得查询和分析它们变得容易,并提取可用于进一步处理的有用信息。

活动适用于ELK,Seq或Splunk等活动商店。

默认情况下不会启用事件 - 但可以在 ConfigureServices 方法中进行全局配置,例如:

services.AddIdentityServer(options =>
{
  options.Events.RaiseSuccessEvents = true;
  options.Events.RaiseFailureEvents = true;
  options.Events.RaiseErrorEvents = true;
});

要发出事件,请使用 IEventService DI容器并调用 RaiseAsync 方法,例如:

public async Task<IActionResult> Login(LoginInputModel model)
{
  if (_users.ValidateCredentials(model.Username, model.Password))
  {
      // issue authentication cookie with subject ID and username
      var user = _users.FindByUsername(model.Username);
      await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username));
  }
  else
  {
      await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
  }
}

自定义接收器

我们的默认事件接收器只是将事件类序列化为JSON并将其转发到ASP.NET Core日志系统。如果要连接到自定义事件存储,请实现该 IEventSink 接口并将其注册到DI。

以下示例使用Seq发出事件:

 public class SeqEventSink : IEventSink
{
  private readonly Logger _log;

  public SeqEventSink()
  {
      _log = new LoggerConfiguration()
          .WriteTo.Seq("http://localhost:5341")
          .CreateLogger();
  }

  public Task PersistAsync(Event evt)
  {
      if (evt.EventType == EventTypes.Success ||
          evt.EventType == EventTypes.Information)
      {
          _log.Information("{Name} ({Id}), Details: {@details}",
              evt.Name,
              evt.Id,
              evt);
      }
      else
      {
          _log.Error("{Name} ({Id}), Details: {@details}",
              evt.Name,
              evt.Id,
              evt);
      }

      return Task.CompletedTask;
  }
}

Serilog.Sinks.Seq 包添加到主机以使上述代码有效。

IdentityServer中定义了以下事件:

ApiAuthenticationFailureEvent ApiAuthenticationSuccessEvent

获取内省端点上成功/失败的API身份验证。

ClientAuthenticationSuccessEvent ClientAuthenticationFailureEvent

获取在令牌端点处成功/失败的客户端身份验证。

TokenIssuedSuccessEvent TokenIssuedFailureEvent

获取成功/失败尝试请求身份令牌,访问令牌,刷新令牌和授权码。

TokenIntrospectionSuccessEvent TokenIntrospectionFailureEvent

获取成功的令牌内省请求。

TokenRevokedSuccessEvent

获取成功的令牌吊销请求。

UserLoginSuccessEvent UserLoginFailureEvent

成功/失败用户登录的快速入门UI引发。

UserLogoutSuccessEvent

获取成功的注销请求。

ConsentGrantedEvent ConsentDeniedEvent

在同意UI中引发。

UnhandledExceptionEvent

获取未处理的异常。

自定义事件

您可以创建自己的事件并通过我们的基础架构发出它们。

您需要从我们的基 Event 类派生,该基类注入活动ID,时间戳等上下文信息。您的派生类可以添加特定于事件上下文的任意数据字段:

public class UserLoginFailureEvent : Event
{
  public UserLoginFailureEvent(string username, string error)
      : base(EventCategories.Authentication,
              "User Login Failure",
              EventTypes.Failure,
              EventIds.UserLoginFailure,
              error)
  {
      Username = username;
  }

  public string Username { get; set; }
}
密码学,密钥和

IdentityServer依赖于几个加密机制来完成其工作。

令牌签名和验证

IdentityServer需要非对称密钥对来签署和验证JWT。此密钥对可以是证书/私钥组合或原始RSA密钥。无论如何,它必须支持带有SHA256的RSA。

加载签名密钥和相应的验证部分是通过 ISigningCredentialStore 和的实现来完成的 IValidationKeysStore 。如果要自定义加载密钥,可以实现这些接口并将其注册到DI。

DI构建器扩展有几种方便的方法来设置签名和验证密钥 - 请参阅此处。

签名密钥翻转

虽然一次只能使用一个签名密钥,但您可以将多个验证密钥发布到发现文档。这对于密钥翻转很有用。

翻转通常如下所示:

您请求/创建新的密钥材料

除了当前的验证密钥之外,还要发布新的验证密钥。您可以使用 AddValidationKeys 构建器扩展方法。

所有客户端和API现在都有机会在下次更新发现文档的本地副本时了解新密钥

在一定时间(例如24小时)之后,所有客户端和API现在应该接受旧密钥材料和新密钥材料

只要你愿意,就可以保留旧的密钥材料,也许你有需要验证的长寿命令牌

当旧密钥材料不再使用时,将其退出

所有客户端和API将在下次更新发现文档的本地副本时“忘记”旧密钥

这要求客户端和API使用发现文档,并且还具有定期刷新其配置的功能。

ASP.NET Core中的Cookie身份验证(或MVC中的防伪)使用ASP.NET Core数据保护功能。根据您的部署方案,这可能需要其他配置。有关更多信息,请参阅Microsoft 文档。

HTTPS

我们不强制使用HTTPS,但对于生产,它必须与IdentityServer进行每次交互。

授权类型是指定客户端如何与IdentityServer交互的方式。OpenID Connect和OAuth 2规范定义了以下授权类型:

资源所有者密码

您可以通过配置上的 AllowedGrantTypes 属性指定客户端可以使用的授权类型 Client

可以将客户端配置为使用多个授权类型(例如,用于以用户为中心的操作的混合和用于服务器到服务器通信的客户端凭证)。的 GrantTypes 类可以用来从典型交付式的组合,以挑选:

Client.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;

您也可以手动指定授权类型列表:

Client.AllowedGrantTypes =
{
  GrantType.Hybrid,
  GrantType.ClientCredentials,
  "my_custom_grant_type"
};

如果要通过浏览器通道传输访问令牌,还需要在客户端配置上明确允许:

Client.AllowAccessTokensViaBrowser = true;

出于安全原因,并非所有授权类型组合都是允许的。请参阅下面的更多细节。

对于其余部分,简要描述了授权类型,以及何时使用它们。还建议您另外阅读相应的规格以更好地理解差异。

客户端凭证

这是最简单的授权类型,用于服务器到服务器通信 - 始终代表客户端而不是用户请求令牌。

使用此授权类型,您可以向令牌端点发送令牌请求,并获取代表客户端的访问令牌。客户端通常必须使用其客户端ID和密钥对令牌端点进行身份验证。

有关如何使用它的示例,请参阅“ 客户端凭据快速入门 ”。

资源所有者密码

资源所有者密码授予类型允许通过将用户的名称和密码发送到令牌端点来代表用户请求令牌。这就是所谓的“非交互式”身份验证,通常不推荐使用。

某些遗留或第一方集成方案可能有原因,其中此授权类型很有用,但一般建议使用隐式或混合的交互式流来代替用户身份验证。

有关如何使用它的示例,请参阅资源所有者密码快速入门。您还需要提供用户名/密码验证的代码,可以通过实现 IResourceOwnerPasswordValidator 接口来提供。您可以在此处找到有关此界面的更多信息。

隐式授权类型针对基于浏览器的应用程序进行了优化。仅用于用户身份验证(服务器端和JavaScript应用程序),或身份验证和访问令牌请求(JavaScript应用程序)。

在隐式流程中,所有令牌都通过浏览器传输,因此不允许使用刷新令牌等高级功能。

该快速入门显示了服务端的web应用程序的认证,而 这个显示的JavaScript。

授权代码流最初由OAuth 2指定,并提供了一种在反向通道上检索令牌而不是浏览器前端通道的方法。它还支持客户端身份验证。

虽然这种授权类型本身是受支持的,但通常建议您将其与身份令牌结合使用,将其转换为所谓的混合流。混合流程为您提供重要的额外功能,如签名协议响应。

混合流是隐式和授权代码流的组合 - 它使用多种授权类型的组合,最典型的是。 code id_token

在混合流中,身份令牌通过浏览器通道传输,并包含签名协议响应以及其他工件(如授权代码)的签名。这减轻了许多适用于浏览器通道的攻击。成功验证响应后,反向通道用于检索访问和刷新令牌。

这是希望检索访问令牌(也可能是刷新令牌)的本机应用程序的推荐流程,用于服务器端Web应用程序和本机桌面/移动应用程序。

有关将混合流与MVC一起使用的更多信息,请参阅此快速入门。

刷新令牌允许获得对API的长期访问。

您通常希望尽可能缩短访问令牌的生命周期,但同时不希望通过对IdentityServer进行前端通道往返来请求新的令牌一次又一次地打扰用户。

刷新令牌允许在没有用户交互的情况下请求新的访问令牌。每次客户端刷新令牌时,都需要对IdentityServer进行(经过身份验证的)反向通道调用。这允许检查刷新令牌是否仍然有效,或者在此期间是否已被撤销。

混合,授权代码和资源所有者密码流支持刷新令牌。要请求刷新令牌,客户端需要 offline_access 在令牌请求中包含范围(并且必须被授权请求该范围)。

扩展授权允许使用新的授权类型扩展令牌端点。有关详细信息,请参阅此

不兼容的授权类型

禁止使用某些授权类型组合:

混合隐式和授权代码或混合将允许从更安全的基于代码的流的降级攻击到隐式。

同样存在允许授权代码和混合代码的问题

在某些情况下,客户端需要使用身份服务器进行身份验证,例如

在令牌端点请求令牌的机密应用程序(也称为客户端)

API在内省端点验证引用令牌

为此,您可以将秘密列表分配给客户端或API资源。

秘密解析和验证是身份服务器中的可扩展点,开箱即用它支持共享机密以及通过基本身份验证头或POST主体传输共享机密。

创建共享秘密

以下代码设置散列共享密钥:

var secret = new Secret("secret".Sha256());

现在可以将此秘密分配给a Client 或an ApiResource 。请注意,它们不仅支持单个秘密,还支持多个秘密。这对于秘密翻转和轮换非常有用:

var client = new Client
{
  ClientId = "client",
  ClientSecrets = new List<Secret> { secret },

  AllowedGrantTypes = GrantTypes.ClientCredentials,
  AllowedScopes = new List<string>
  {
      "api1", "api2"
  }
};

实际上,您还可以为秘密分配说明和到期日期。该描述将用于记录,以及强制执行秘密生存期的到期日期:

var secret = new Secret(
  "secret".Sha256(),
  "2016 secret",
  new DateTime(2016, 12, 31));

使用共享密钥进行身份验证

您可以将客户端ID /机密组合作为POST正文的一部分发送:

POST /connect/token

client_id=client1&
client_secret=secret&
...

..或作为基本身份验证标头:

POST /connect/token

Authorization: Basic xxxxx

...

您可以使用以下C#代码手动创建基本身份验证标头:

var credentials = string.Format("{0}:{1}", clientId, clientSecret);
var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", headerValue);

该IdentityModel库有一个叫做辅助类 TokenClient IntrospectionClient 封装认证和协议消息。

超越共享秘密

还有其他技术来验证客户端,例如基于公钥/私钥加密。IdentityServer包括对私钥JWT客户机密钥的支持(请参阅RFC 7523)。

秘密可扩展性通常包含三件事:

一个秘密的定义

一个知道如何从传入请求中提取秘密的秘密解析器

一个秘密验证器,知道如何根据定义验证解析的秘密

秘密解析器和验证器是 ISecretParser ISecretValidator 接口的实现。要使它们可用于IdentityServer,您需要将它们注册到DI容器,例如:

builder.AddSecretParser<ClientAssertionSecretParser>()
builder.AddSecretValidator<PrivateKeyJwtSecretValidator>()

我们的默认私钥JWT秘密验证器期望完整(叶)证书作为秘密定义的base64。然后,此证书将用于验证自签名JWT上的签名,例如:

var client = new Client
{
  ClientId = "client.jwt",
  ClientSecrets =
  {
      new Secret
      {
          Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
          Value = "MIIDATCCAe2gAwIBAgIQoHUYAquk9rBJcq8W+F0FAzAJBgUrDgMCHQUAMBIxEDAOBgNVBAMTB0RldlJvb3QwHhcNMTAwMTIwMjMwMDAwWhcNMjAwMTIwMjMwMDAwWjARMQ8wDQYDVQQDEwZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSaY4x1eXqjHF1iXQcF3pbFrIbmNw19w/IdOQxbavmuPbhY7jX0IORu/GQiHjmhqWt8F4G7KGLhXLC1j7rXdDmxXRyVJBZBTEaSYukuX7zGeUXscdpgODLQVay/0hUGz54aDZPAhtBHaYbog+yH10sCXgV1Mxtzx3dGelA6pPwiAmXwFxjJ1HGsS/hdbt+vgXhdlzud3ZSfyI/TJAnFeKxsmbJUyqMfoBl1zFKG4MOvgHhBjekp+r8gYNGknMYu9JDFr1ue0wylaw9UwG8ZXAkYmYbn2wN/CpJl3gJgX42/9g87uLvtVAmz5L+rZQTlS1ibv54ScR2lcRpGQiQav/LAgMBAAGjXDBaMBMGA1UdJQQMMAoGCCsGAQUFBwMCMEMGA1UdAQQ8MDqAENIWANpX5DZ3bX3WvoDfy0GhFDASMRAwDgYDVQQDEwdEZXZSb290ghAsWTt7E82DjU1E1p427Qj2MAkGBSsOAwIdBQADggEBADLje0qbqGVPaZHINLn+WSM2czZk0b5NG80btp7arjgDYoWBIe2TSOkkApTRhLPfmZTsaiI3Ro/64q+Dk3z3Kt7w+grHqu5nYhsn7xQFAQUf3y2KcJnRdIEk0jrLM4vgIzYdXsoC6YO+9QnlkNqcN36Y8IpSVSTda6gRKvGXiAhu42e2Qey/WNMFOL+YzMXGt/nDHL/qRKsuXBOarIb++43DV3YnxGTx22llhOnPpuZ9/gnNY7KLjODaiEciKhaKqt/b57mTEz4jTF4kIg6BP03MUfDXeVlM1Qf1jB43G2QQ19n5lUiqTpmQkcfLfyci2uBZ8BkOhXr3Vk9HIk/xBXQ="
      }
  },

  AllowedGrantTypes = GrantTypes.ClientCredentials,
  AllowedScopes = { "api1", "api2" }
};

您可以实现自己的秘密验证器(或扩展我们的秘密验证器)来实现例如链信任验证。

OAuth 2.0定义了令牌端点的标准授权类型,例如 password authorization_code refresh_token 。扩展授权是一种添加对非标准令牌颁发方案(如令牌转换,委派或自定义凭据)的支持的方法。

您可以通过实现 IExtensionGrantValidator 接口添加对其他授权类型的支持:

public interface IExtensionGrantValidator
{
  /// <summary>
  /// Handles the custom grant request.
  /// </summary>
  /// <param name="request">The validation context.</param>
  Task ValidateAsync(ExtensionGrantValidationContext context);

  /// <summary>
  /// Returns the grant type this validator can deal with
  /// </summary>
  /// <value>
  /// The type of the grant.
  /// </value>
  string GrantType { get; }
}

ExtensionGrantValidationContext 对象使您可以访问:

传入令牌请求 - 众所周知的验证值,以及任何自定义值(通过 Raw 集合)

结果 - 错误或成功

自定义响应参数

要注册扩展授权,请将其添加到DI:

builder.AddExtensionGrantValidator<MyExtensionsGrantValidator>();

示例:使用扩展授权的简单委派

想象一下以下场景 - 前端客户端使用通过交互流(例如混合流)获取的令牌调用中间层API。此中间层API(API 1)现在希望代表交互式用户调用后端API(API 2):

换句话说,中间层API(API 1)需要包含用户身份的访问令牌,但需要具有后端API(API 2)的范围。

您可能听说过 穷人代表团 这一术语,前端的访问令牌只是转发到后端。这有一些缺点,例如 API 2 现在必须接受 API 1 范围,这将允许用户直接调用 API 2 。此外 - 您可能希望在令牌中添加一些特定于委托的声明,例如,呼叫路径是通过 API 1 的事实。

实施扩展授权

前端会将令牌发送到API 1,现在需要在IdentityServer上使用API 2的新令牌交换此令牌。

在线上,对交换的令牌服务的调用可能如下所示:

POST /connect/token

grant_type=delegation&
scope=api2&
token=...&
client_id=api1.client
client_secret=secret

扩展授权验证程序的工作是通过验证传入令牌并返回表示新令牌的结果来处理该请求:

public class DelegationGrantValidator : IExtensionGrantValidator
{
  private readonly ITokenValidator _validator;

  public DelegationGrantValidator(ITokenValidator validator)
  {
      _validator = validator;
  }

  public string GrantType => "delegation";

  public async Task ValidateAsync(ExtensionGrantValidationContext context)
  {
      var userToken = context.Request.Raw.Get("token");

      if (string.IsNullOrEmpty(userToken))
      {
          context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
          return;
      }

      var result = await _validator.ValidateAccessTokenAsync(userToken);
      if (result.IsError)
      {
          context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
          return;
      }

      // get user's identity
      var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;

      context.Result = new GrantValidationResult(sub, GrantType);
      return;
  }
}

不要忘记在DI上注册验证器。

注册委托客户端

您需要在IdentityServer中进行客户端注册,以允许客户端使用此新的扩展授权,例如:

var client = new client
{
  ClientId = "api1.client",
  ClientSecrets = new List<Secret>
  {
      new Secret("secret".Sha256())
  },

  AllowedGrantTypes = { "delegation" },

  AllowedScopes = new List<string>
  {
      "api2"
  }
}

调用令牌端点

在API 1中,您现在可以自己构建HTTP有效内容,或使用 IdentityModel 帮助程序库:

public async Task<TokenResponse> DelegateAsync(string userToken)
{
  var payload = new
  {
      token = userToken
  };

  // create token client
  var client = new TokenClient(disco.TokenEndpoint, "api1.client", "secret");

  // send custom grant to token endpoint, return response
  return await client.RequestCustomGrantAsync("delegation", "api2", payload);
}

现在 TokenResponse.AccessToken 将包含委托访问令牌。

资源所有者密码验证

如果要使用OAuth 2.0资源所有者密码凭据授权(aka password ),则需要实现并注册 IResourceOwnerPasswordValidator 接口:

public interface IResourceOwnerPasswordValidator
{
  /// <summary>
  /// Validates the resource owner password credential
  /// </summary>
  /// <param name="context">The context.</param>
  Task ValidateAsync(ResourceOwnerPasswordValidationContext context);
}

在上下文中,您将找到已解析的协议参数,如 UserName Password ,以及原始请求,如果您想查看其他输入数据。

然后,您的工作是实施密码验证并相应地设置 Result 上下文。请参阅GrantValidationResult文档

由于访问令牌的生命周期有限,因此刷新令牌允许在没有用户交互的情况下请求新的访问令牌。

以下流程支持刷新令牌:授权代码,混合和资源所有者密码凭据流。需要明确授权客户端通过设置 AllowOfflineAccess 来请求刷新令牌 true

其他客户端设置

AbsoluteRefreshTokenLifetime

刷新令牌的最长生命周期,以秒为单位。默认为2592000秒/ 30天。零允许刷新令牌,当仅在SlidingRefreshTokenLifetime传递后使用时过期。 RefreshTokenExpiration = Sliding

SlidingRefreshTokenLifetime

刷新令牌的生命周期以秒为单位。默认为1296000秒/ 15天

RefreshTokenUsage

ReUse 刷新令牌时刷新令牌句柄将保持不变 OneTime 刷新令牌时将更新刷新令牌句柄

RefreshTokenExpiration

Absolute 刷新令牌将在固定时间点到期(由AbsoluteRefreshTokenLifetime指定) Sliding 刷新令牌时,将刷新刷新令牌的生命周期(按SlidingRefreshTokenLifetime中指定的数量)。生命周期不会超过AbsoluteRefreshTokenLifetime。

UpdateAccessTokenClaimsOnRefresh

获取或设置一个值,该值指示是否应在刷新令牌请求上更新访问令牌(及其声明)。

访问令牌可以有两种形式 - 自包含或参考。

JWT令牌将是一个自包含的访问令牌 - 它是一个带有声明和过期的受保护数据结构。一旦API了解了密钥材料,它就可以验证自包含的令牌,而无需与发行者进行通信。这使得JWT难以撤销。它们将一直有效,直到它们过期。

使用引用令牌时 - IdentityServer会将令牌的内容存储在数据存储中,并且只会将此令牌的唯一标识符发回给客户端。接收此引用的API必须打开与IdentityServer的反向通道通信以验证令牌。

您可以使用以下设置切换客户端的令牌类型:

client.AccessTokenType = AccessTokenType.Reference;

IdentityServer提供了OAuth 2.0内省规范的实现,该规范允许API取消引用令牌。您可以使用我们的专用内省中间件 或使用身份服务器身份验证中间件,它可以验证JWT和引用令牌。

内省端点需要身份验证 - 因为内省端点的客户端是API,您可以在以下位置配置秘密 ApiResource

var api = new ApiResource("api1")
{
  ApiSecrets = { new Secret("secret".Sha256()) }
}

有关如何为API配置IdentityServer身份验证中间件的详细信息,请参阅此处。

IdentityServer中的许多端点将通过基于JavaScript的客户端的Ajax调用进行访问。鉴于IdentityServer最有可能托管在与这些客户端不同的源上,这意味着需要配置跨源资源共享(CORS)。

基于客户端的CORS配置

配置CORS的一种方法是 AllowedCorsOrigins 在客户端配置上使用该集合。只需将客户端的原点添加到集合中,IdentityServer中的默认配置将查询这些值以允许来自源的跨源调用。

配置CORS时,请务必使用原点(不是URL)。例如: https://foo:123/ 是一个URL,而是 https://foo:123 一个原点。

如果您使用我们提供的“内存中”或基于EF的客户端配置,则将使用此默认CORS实现。如果您定义自己的 IClientStore ,那么您将需要实现自己的自定义CORS策略服务(见下文)。

自定义Cors策略服务

IdentityServer允许托管应用程序实现 ICorsPolicyService 完全控制CORS策略。

要实现的单一方法是:。如果允许原点则返回,否则返回。 Task<bool> IsOriginAllowedAsync(string origin)``true``false

实现后,只需在DI中注册实现,然后IdentityServer将使用您的自定义实现。

DefaultCorsPolicyService

如果您只是希望硬编码一组允许的原点,那么 ICorsPolicyService 可以使用一个预先构建的实现调用 DefaultCorsPolicyService 。这将被配置为DI单身,并以其硬编码的 AllowedOrigins 收集,或设置标志 AllowAll ,以 true 允许所有的起源。例如,在 ConfigureServices

var cors = new DefaultCorsPolicyService(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
{
  AllowedOrigins = { "https://foo", "https://bar" }
};
services.AddSingleton<ICorsPolicyService>(cors);

AllowAll 谨慎使用。

将IdentityServer的CORS策略与ASP.NET Core的CORS策略混合

IdentityServer使用ASP.NET Core的CORS中间件来提供其CORS实现。托管IdentityServer的应用程序可能还需要CORS用于自己的自定义端点。通常,两者应该在同一个应用程序中一起工作。

您的代码应使用ASP.NET Core中记录的CORS功能,而不考虑IdentityServer。这意味着您应该定义策略并正常注册中间件。如果您的应用程序定义了策略 ConfigureServices ,那么这些策略应该继续在您使用它们的相同位置(在您配置CORS中间件的位置或在 EnableCors 控制器代码中使用MVC 属性的位置)。相反,如果您使用CORS中间件(通过策略构建器回调)定义内联策略,那么它也应该继续正常工作。

在您使用ASP.NET Core CORS服务和IdentityServer之间可能存在冲突的一种情况是,如果您决定创建自定义 ICorsPolicyProvider 。鉴于ASP.NET Core的CORS服务和中间件的设计,IdentityServer实现了自己的自定义 ICorsPolicyProvider 并将其注册到DI系统中。幸运的是,IdentityServer实现旨在使用装饰器模式来包装 ICorsPolicyProvider 已在DI中注册的任何现有模式 。这意味着你也可以实现 ICorsPolicyProvider ,但它只需要在DI中的IdentityServer之前注册(例如,在 ConfigureServices )。

可以在 https://baseaddress/.well-known/openid-configuration 找到发现文档。它包含有关IdentityServer的端点,密钥材料和功能的信息。

默认情况下,所有信息都包含在发现文档中,但通过使用配置选项,您可以隐藏各个部分,例如:

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 接口(或从我们的默认实现派生)。

添加更多API端点

您可以向托管IdentityServer4的应用程序添加更多API端点。

您通常希望通过它们所托管的IdentityServer实例来保护这些API。这不是问题。只需将令牌验证处理程序添加到主机(请参阅此处):

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();

  // details omitted
  services.AddIdentityServer();

  services.AddAuthentication()
      .AddIdentityServerAuthentication("token", isAuth =>
      {
          isAuth.Authority = "base_address_of_identityserver";
          isAuth.ApiName = "name_of_api";
      });
}

在您的API上,您需要添加 [Authorize] 属性并显式引用您要使用的身份验证方案( token 在此示例中,您可以选择您喜欢的任何名称):

public class TestController : ControllerBase
{
  [Route("test")]
  [Authorize(AuthenticationSchemes = "token")]
  public IActionResult Get()
  {
      var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToArray();
      return Ok(new { message = "Hello API", claims });
  }
}

如果要从浏览器调用该API,则还需要配置CORS(请参阅此处)。

如果需要,您还可以将端点添加到发现文档中,例如:

services.AddIdentityServer(options =>
{
  options.Discovery.CustomEntries.Add("custom_endpoint", "~/api/custom");
})
添加新协议

除了对OpenID Connect和OAuth 2.0的内置支持之外,IdentityServer4还允许添加对其他协议的支持。

您可以将这些附加协议端点添加为中间件或使用例如MVC控制器。在这两种情况下,您都可以访问ASP.NET Core DI系统,该系统允许重用我们的内部服务,例如访问客户端定义或密钥材料。

可以在此处找到添加WS-Federation支持的示例。

典型认证工作流程

身份验证请求通常如下所示:

身份验证请求到达协议端点

协议端点执行输入验证

重定向到登录页面,返回URL设置回协议端点(如果用户是匿名的)

通过访问当前请求详细信息 IIdentityServerInteractionService 用户身份验证(本地或通过外部身份验证中间件)登录用户重定向回协议端点

创建协议响应(令牌创建和重定向回客户端)

有用的IdentityServer服务

要实现上述工作流程,需要与IdentityServer建立一些交互点。

访问配置并重定向到登录页面

您可以通过将 IdentityServerOptions 类注入代码来访问IdentityServer配置。这个,例如具有登录页面的配置路径:

var returnUrl = Url.Action("Index");
returnUrl = returnUrl.AddQueryString(Request.QueryString.Value);

var loginUrl = _options.UserInteraction.LoginUrl;
var url = loginUrl.AddQueryString(_options.UserInteraction.LoginReturnUrlParameter, returnUrl);

return Redirect(url);

登录页面与当前协议请求之间的交互

所述 IIdentityServerInteractionService 支撑件转动一个协议返回URL成解析和验证上下文对象:

var context = await _interaction.GetAuthorizationContextAsync(returnUrl);

默认情况下,交互服务仅了解OpenID Connect协议消息。要扩展支持,您可以自己编写 IReturnUrlParser

public interface IReturnUrlParser
{
  bool IsValidReturnUrl(string returnUrl);
  Task<AuthorizationRequest> ParseAsync(string returnUrl);
}

..然后在DI中注册解析器:

builder.Services.AddTransient<IReturnUrlParser, WsFederationReturnUrlParser>();

这允许登录页面获取客户端配置和其他协议参数等信息。

访问用于创建协议响应的配置和密钥材料

通过将 IKeyMaterialService 代码注入到代码中,您可以访问配置的签名凭据和验证密钥:

工具
该IdentityServerTools班是为IdentityServer编写扩展代码时,你可能需要有效的内部工具的集合。要使用它,请将其注入代码,例如控制器:

public MyController(IdentityServerTools tools)
{
  _tools = tools;
}
该IssueJwtAsync方法允许使用IdentityServer令牌创建引擎创建JWT令牌。这IssueClientJwtAsync是用于为服务器到服务器通信创建令牌的简单版本(例如,当您必须从代码中调用受IdentityServer保护的API时):

public async Task<IActionResult> MyAction()
{
  var token = await _tools.IssueClientJwtAsync(
      clientId: "client_id",
      lifetime: 3600,
      audiences: new[] { "backend.api" });

  // more code
}var credential = await _keys.GetSigningCredentialsAsync();
var key = credential.Key as Microsoft.IdentityModel.Tokens.X509SecurityKey;

var descriptor = new SecurityTokenDescriptor
{
  AppliesToAddress = result.Client.ClientId,
  Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddSeconds(result.Client.IdentityTokenLifetime)),
  ReplyToAddress = result.Client.RedirectUris.First(),
  SigningCredentials = new X509SigningCredentials(key.Certificate, result.RelyingParty.SignatureAlgorithm, result.RelyingParty.DigestAlgorithm),
  Subject = outgoingSubject,
  TokenIssuerName = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(),
  TokenType = result.RelyingParty.TokenType
};

发现端点可用于检索有关IdentityServer的元数据 - 它返回诸如颁发者名称,密钥材料,支持的范围等信息。有关详细信息,请参阅规范。

发现端点可通过/.well-known/openid-configuration相对于基地址获得,例如:

https://demo.identityserver.io/.well-known/openid-configuration

您可以使用IdentityModel客户端库以编程方式从.NET代码访问发现端点。有关更多信息,请查看IdentityModel 文档。

授权端点可用于通过浏览器请求令牌或授权码。此过程通常涉及最终用户的身份验证和可选的同意。

IdentityServer支持OpenID Connect和OAuth 2.0授权请求参数的子集。有关完整列表,请参见此处。

client_id

客户的标识符(必填)。

scope

一个或多个注册范围(必填)

redirect_uri

必须与该客户端允许的重定向URI之一完全匹配(必需)

response_type

id_token 请求身份令牌(仅允许身份范围) token 请求访问令牌(仅允许资源范围) id_token token 请求身份令牌和访问令牌 code 请求授权码 code id_token 请求授权代码和身份令牌 code id_token token 请求授权代码,身份令牌和访问令牌

response_mode

form_post 将令牌响应作为表单发送而不是片段编码重定向(可选)

state

identityserver将回显令牌响应的状态值,这是客户端和提供者之间的往返状态,关联请求和响应以及CSRF /重放保护。(推荐的)

nonce

identityserver将回显身份令牌中的nonce值,这是为了重放保护) 通过隐式授权对身份令牌是必需的。

prompt

none 请求期间不会显示任何UI。如果这是不可能的(例如,因为用户必须登录或同意),则返回错误 login 即使用户已登录并具有有效会话,也会显示登录UI

code_challenge

发送PKCE的代码质询

code_challenge_method

plain 表示挑战是使用纯文本(不推荐) S256 表示使用SHA256对挑战进行哈希处理

login_hint

可用于预先填写登录页面上的用户名字段

ui_locales

提供有关登录UI所需显示语言的提示

max_age

如果用户的登录会话超过最大年龄(以秒为单位),将显示登录UI

acr_values

允许传递额外的身份验证相关信息 - 身份服务器特殊情况下面的私有acr_values: idp:name_of_idp 绕过login / home领域屏幕并将用户直接转发到选定的身份提供者(如果允许每个客户端配置) tenant:name_of_tenant 可用于将租户名称传递给登录UI

GET /connect/authorize?
  client_id=client1&
  scope=openid email api1&
  response_type=id_token token&
  redirect_uri=https://myapp/callback&
  state=abc&
  nonce=xyz

(删除了URL编码,并添加了换行符以提高可读性)

您可以使用IdentityModel客户端库以编程方式创建授权请求.NET代码。有关更多信息,请查看IdentityModel 文档。

令牌端点可用于以编程方式请求令牌。它支持 password authorization_code client_credentials refresh_token 补助的类型)。此外,可以扩展令牌端点以支持扩展授权类型。

IdentityServer支持OpenID Connect和OAuth 2.0令牌请求参数的子集。有关完整列表,请参见此处。

client_id

客户标识符(必填)

client_secret

客户端密钥可以在帖子正文中,也可以作为基本身份验证标头。可选的。

grant_type

authorization_code client_credentials password refresh_token 或自定义

scope

一个或多个注册范围。如果未指定,将发出所有明确允许范围的标记。

redirect_uri

authorization_code 授权类型所需

授权码( authorization_code 授权类型所需)

code_verifier

PKCE证明密钥

username

资源所有者用户名( password 授予类型所需)

password

资源所有者密码( password 授予类型所需)

acr_values

允许为 password 授权类型传递额外的身份验证相关信息- identityserver特殊情况下面的专有acr_values: idp:name_of_idp 绕过login / home领域屏幕并将用户直接转发到选定的身份提供者(如果允许每个客户端配置) tenant:name_of_tenant 可用于将租户名称传递给令牌端点

refresh_token

刷新令牌( refresh_token 授予类型所需)

POST /connect/token

  client_id=client1&
  client_secret=secret&
  grant_type=authorization_code&
  code=hdh922&
  redirect_uri=https://myapp.com/callback

(为了便于阅读,删除了表格编码并添加了换行符)

您可以使用IdentityModel客户端库以编程方式从.NET代码访问令牌端点。有关更多信息,请查看IdentityModel 文档。

UserInfo端点

UserInfo端点可用于检索有关用户的身份信息(请参阅规范)。

调用者需要发送代表用户的有效访问令牌。根据授予的范围,UserInfo端点将返回映射的声明(至少需要openid作用域)。

GET /connect/userinfo
Authorization: Bearer <access_token>
HTTP/1.1 200 OK
Content-Type: application/json

{
  "sub": "248289761001",
  "name": "Bob Smith",
  "given_name": "Bob",
  "family_name": "Smith",
  "role": [
      "user",
      "admin"
  ]
}

您可以使用IdentityModel客户端库以编程方式从.NET代码访问userinfo端点。有关更多信息,请查看IdentityModel 文档。

内省端点是RFC 7662的实现。

它可用于验证引用令牌(如果消费者不支持适当的JWT或加密库,则可以使用JWT)。内省端点需要身份验证 - 因为内省端点的客户端是API,您可以在上配置密码 ApiResource

POST /connect/introspect
Authorization: Basic xxxyyy

token=<token>

成功的响应将返回状态代码200以及活动或非活动令牌:

{
  "active": true,
  "sub": "123"
}

未知或过期的令牌将被标记为无效:

{
  "active": false,
}

无效请求将返回400,未授权请求401。

您可以使用IdentityModel客户端库以编程方式从.NET代码访问内省端点。有关更多信息,请查看IdentityModel 文档。

此端点允许撤消访问令牌(仅限引用令牌)和刷新令牌。它实现了令牌撤销规范(RFC 7009)。

token

要撤销的令牌(必填)

token_type_hint

或者 access_token refresh_token (可选)

POST /connect/revocation HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token

您可以使用IdentityModel客户端库以编程方式从.NET代码访问吊销端点。有关更多信息,请查看IdentityModel 文档。

结束会话端点

结束会话端点可用于触发单点注销(请参阅规范)。

要使用结束会话端点,客户端应用程序会将用户的浏览器重定向到结束会话URL。用户在会话期间通过浏览器登录的所有应用程序都可以参与注销。

终端会话端点的URL可通过发现端点获得。

id_token_hint

当用户被重定向到端点时,系统会提示他们是否真的想要注销。发送从身份验证收到的原始 id_token 的客户端可以绕过此提示。这是作为查询的字符串参数传递的 id_token_hint

post_logout_redirect_uri

如果 id_token_hint 传递了有效,则客户端也可以发送 post_logout_redirect_uri 参数。这可用于允许用户在注销后重定向回客户端。该值必须与客户端预先配置的PostLogoutRedirectUris(客户端文档)之一匹配。

如果 post_logout_redirect_uri 传递了有效,则客户端也可以发送 state 参数。在用户重定向回客户端后,这将作为查询字符串参数返回给客户端。这通常由客户端用于跨重定向的往返状态。

GET /connect/endsession?id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IjdlOGFkZmMzMjU1OTEyNzI0ZDY4NWZmYmIwOThjNDEyIiwidHlwIjoiSldUIn0.eyJuYmYiOjE0OTE3NjUzMjEsImV4cCI6MTQ5MTc2NTYyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoianNfb2lkYyIsIm5vbmNlIjoiYTQwNGFjN2NjYWEwNGFmNzkzNmJjYTkyNTJkYTRhODUiLCJpYXQiOjE0OTE3NjUzMjEsInNpZCI6IjI2YTYzNWVmOTQ2ZjRiZGU3ZWUzMzQ2ZjFmMWY1NTZjIiwic3ViIjoiODg0MjExMTMiLCJhdXRoX3RpbWUiOjE0OTE3NjUzMTksImlkcCI6ImxvY2FsIiwiYW1yIjpbInB3ZCJdfQ.STzOWoeVYMtZdRAeRT95cMYEmClixWkmGwVH2Yyiks9BETotbSZiSfgE5kRh72kghN78N3-RgCTUmM2edB3bZx4H5ut3wWsBnZtQ2JLfhTwJAjaLE9Ykt68ovNJySbm8hjZhHzPWKh55jzshivQvTX0GdtlbcDoEA1oNONxHkpDIcr3pRoGi6YveEAFsGOeSQwzT76aId-rAALhFPkyKnVc-uB8IHtGNSyRWLFhwVqAdS3fRNO7iIs5hYRxeFSU7a5ZuUqZ6RRi-bcDhI-djKO5uAwiyhfpbpYcaY_TxXWoCmq8N8uAw9zqFsQUwcXymfOAi2UF3eFZt02hBu-shKA&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A7017%2Findex.html

您可以使用IdentityModel客户端库以编程方式创建end_session请求.NET代码。有关更多信息,请查看IdentityModel 文档。

此类为身份资源建模。

Enabled

指示此资源是否已启用且可以请求。默认为true。

身份资源的唯一名称。这是客户端将用于授权请求中的scope参数的值。

DisplayName

该值将用于例如同意屏幕上。

Description

该值将用于例如同意屏幕上。

Required

指定用户是否可以在同意屏幕上取消选择范围(如果同意屏幕要实现此类功能)。默认为false。

Emphasize

指定同意屏幕是否会强调此范围(如果同意屏幕要实现此类功能)。将此设置用于敏感或重要范围。默认为false。

ShowInDiscoveryDocument

指定此范围是否显示在发现文档中。默认为true。

UserClaims

应包含在身份令牌中的关联用户声明类型的列表。

API资源

此类为API资源建模。

Enabled

指示此资源是否已启用且可以请求。默认为true。

API的唯一名称。此值用于内省身份验证,并将添加到传出访问令牌的受众。

DisplayName

该值可以在例如同意屏幕上使用。

Description

该值可以在例如同意屏幕上使用。

ApiSecrets

API密钥用于内省端点。API可以使用API名称和密钥进行内省验证。

UserClaims

应包含在访问令牌中的关联用户声明类型的列表。

Scopes

API必须至少有一个范围。每个范围可以有不同的设置。

在简单的情况下,API只有一个范围。但是在某些情况下,您可能希望细分API的功能,并让不同的客户端访问不同的部分。

范围的唯一名称。这是客户端将用于授权/令牌请求中的scope参数的值。

DisplayName

该值可以在例如同意屏幕上使用。

Description

该值可以在例如同意屏幕上使用。

Required

指定用户是否可以在同意屏幕上取消选择范围(如果同意屏幕要实现此类功能)。默认为false。

Emphasize

指定同意屏幕是否会强调此范围(如果同意屏幕要实现此类功能)。将此设置用于敏感或重要范围。默认为false。

ShowInDiscoveryDocument

指定此范围是否显示在发现文档中。默认为true。

UserClaims

应包含在访问令牌中的关联用户声明类型的列表。此处指定的声明将添加到为API指定的声明列表中。

便利构造函数行为

只是关于为 ApiResource 类提供的构造函数的注释。

要完全控制数据 ApiResource ,请使用不带参数的默认构造函数。如果要为每个API配置多个范围,可以使用此方法。例如:

new ApiResource
{
  Name = "api2",

  Scopes =
  {
      new Scope()
      {
          Name = "api2.full_access",
          DisplayName = "Full access to API 2"
      },
      new Scope
      {
          Name = "api2.read_only",
          DisplayName = "Read only access to API 2"
      }
  }
}

对于每个API只需要一个范围的简单方案,则提供了几个接受a的便捷构造函数 name 。例如:

new ApiResource("api1", "Some API 1")

使用便捷构造函数等同于:

new ApiResource
{
  Name = "api1",
  DisplayName = "Some API 1",

  Scopes =
  {
      new Scope()
      {
          Name = "api1",
          DisplayName = "Some API 1"
      }
  }
}

Client 级车型的ID连接或OAuth 2.0用户端-例如,本地应用,Web应用程序或基于JS的应用程序。

Enabled

指定是否启用客户端。默认为true。

ClientId

客户端的唯一ID

ClientSecrets

客户端机密列表 - 访问令牌端点的凭据。

RequireClientSecret

指定此客户端是否需要密钥才能从令牌端点请求令牌(默认为 true

AllowedGrantTypes

指定允许客户端使用的授权类型。将该 GrantTypes 类用于常见组合。

RequirePkce

指定使用基于授权代码的授权类型的客户端是否必须发送校验密钥

AllowPlainTextPkce

指定使用PKCE的客户端是否可以使用纯文本代码质询(不推荐 - 默认为 false

RedirectUris

指定允许的URI以返回令牌或授权码

AllowedScopes

默认情况下,客户端无权访问任何资源 - 通过添加相应的范围名称来指定允许的资源

AllowOfflineAccess

指定此客户端是否可以请求刷新令牌(请求 offline_access 范围)

AllowAccessTokensViaBrowser

指定是否允许此客户端通过浏览器接收访问令牌。这对于强化允许多种响应类型的流是有用的(例如,通过禁止混合流客户端,该客户端应该使用代码id_token来添加令牌响应类型,从而将令牌泄露给浏览器。

Properties

字典可根据需要保存任何自定义客户端特定值。

认证/注销

PostLogoutRedirectUris

指定在注销后重定向到的允许URI。有关更多详细信息,请参阅OIDC Connect会话管理规范。

FrontChannelLogoutUri

指定客户端的注销URI,以用于基于HTTP的前端通道注销。有关详细信息,请参阅OIDC Front-Channel规范。

FrontChannelLogoutSessionRequired

指定是否应将用户的会话ID发送到FrontChannelLogoutUri。默认为true。

BackChannelLogoutUri

指定客户端的注销URI,以用于基于HTTP的反向通道注销。有关详细信息,请参阅OIDC Back-Channel规范。

BackChannelLogoutSessionRequired

指定是否应在请求中将用户的会话ID发送到BackChannelLogoutUri。默认为true。

EnableLocalLogin

指定此客户端是否可以仅使用本地帐户或外部IdP。默认为true。

IdentityProviderRestrictions

指定可以与此客户端一起使用的外部IdP(如果列表为空,则允许所有IdP)。默认为空。

IdentityTokenLifetime

标识令牌的生命周期(以秒为单位)(默认为300秒/ 5分钟)

AccessTokenLifetime

访问令牌的生命周期(以秒为单位)(默认为3600秒/ 1小时)

AuthorizationCodeLifetime

授权代码的生命周期(以秒为单位)(默认为300秒/ 5分钟)

AbsoluteRefreshTokenLifetime

刷新令牌的最长生命周期,以秒为单位。默认为2592000秒/ 30天

SlidingRefreshTokenLifetime

刷新令牌的生命周期以秒为单位。默认为1296000秒/ 15天

RefreshTokenUsage

ReUse 刷新令牌时刷新令牌句柄将保持不变 OneTime 刷新令牌时将更新刷新令牌句柄。这是默认值。

RefreshTokenExpiration

Absolute 刷新令牌将在固定时间点到期(由AbsoluteRefreshTokenLifetime指定) Sliding 刷新令牌时,将刷新刷新令牌的生命周期(按SlidingRefreshTokenLifetime中指定的数量)。生命周期不会超过AbsoluteRefreshTokenLifetime。

UpdateAccessTokenClaimsOnRefresh

获取或设置一个值,该值指示是否应在刷新令牌请求上更新访问令牌(及其声明)。

AccessTokenType

指定访问令牌是引用令牌还是自包含JWT令牌(默认为Jwt)。

IncludeJwtId

指定JWT访问令牌是否应具有嵌入的唯一ID(通过jti声明)。

AllowedCorsOrigins

如果指定,将由默认CORS策略服务实现(内存和EF)用于为JavaScript客户端构建CORS策略。

Claims

允许客户端的设置声明(将包含在访问令牌中)。

AlwaysSendClientClaims

如果设置,将为每个流发送客户端声明。如果不是,仅用于客户端凭证流(默认为false)

AlwaysIncludeUserClaimsInIdToken

在请求id令牌和访问令牌时,如果用户声明始终将其添加到id令牌而不是请求客户端使用userinfo端点。默认值为false。

ClientClaimsPrefix

如果设置,前缀客户端声明类型将以前缀为。默认为client_。目的是确保它们不会意外地与用户声明冲突。

PairWiseSubjectSalt

对于此客户端的用户,在成对的subjectId生成中使用的salt值。

RequireConsent

指定是否需要同意屏幕。默认为true。

AllowRememberConsent

指定用户是否可以选择存储同意决策。默认为true。

ConsentLifetime

用户同意的生命周期,以秒为单位。默认为null(无到期)。

ClientName

客户端显示名称(用于记录和同意屏幕)

ClientUri

有关客户端的更多信息的URI(在同意屏幕上使用)

LogoUri

URI到客户端徽标(在同意屏幕上使用)

GrantValidationResult

GrantValidationResult 级车型补助确认为扩展赠款和资源所有者密码授权的结果。

最常见的用法是使用身份(成功案例)新建它:

context.Result = new GrantValidationResult(
  subject: "818727",
  authenticationMethod: "custom",
  claims: optionalClaims);

...或使用错误和描述(失败案例):

context.Result = new GrantValidationResult(
  TokenRequestErrors.InvalidGrant,
  "invalid custom credential");

在这两种情况下,您都可以传递将包含在令牌响应中的其他自定义值。

个人资料服务

IdentityServer通常在创建令牌或处理对userinfo或内省端点的请求时需要有关用户的身份信息。默认情况下,IdentityServer仅在身份验证cookie中具有声明,以便为此身份数据进行绘制。

将用户所需的所有可能声明放入cookie中是不切实际的,因此IdentityServer定义了一个扩展点,允许根据用户需要动态加载声明。这个可扩展点是 IProfileService 开发人员通常可以实现此接口来访问包含用户身份数据的自定义数据库或API。

IProfileService的API

GetProfileDataAsync

预期为用户加载声明的API。它传递了一个实例 ProfileDataRequestContext

IsActiveAsync

预期用于指示当前是否允许用户获取令牌的API。它传递了一个实例 IsActiveContext

ProfileDataRequestContext

模拟用户声明的请求,并且是返回这些声明的工具。它包含以下属性:

Subject

ClaimsPrincipal 模型的用户。

Client

Client 用于正被请求的权利要求。

RequestedClaimTypes

请求的索赔类型集合。

Caller

正在请求声明的上下文的标识符(例如,身份令牌,访问令牌或用户信息端点)。常量 IdentityServerConstants.ProfileDataCallers 包含不同的常量值。

IssuedClaims

Claim 将返回的s 列表。预计这将由自定义 IProfileService 实现填充。

AddRequestedClaims

扩展方法对 ProfileDataRequestContext 填充 IssuedClaims ,但首先过滤基于的声明 RequestedClaimTypes

请求的范围和声明映射

客户端请求的范围控制用户声明在令牌中返回给客户端的内容。该 GetProfileDataAsync 方法负责根据上的 RequestedClaimTypes 集合动态获取这些声明 ProfileDataRequestContext

RequestedClaimTypes 集合是基于该定义的用户索赔人口资源的范围进行建模。如果请求的范围是身份资源,则将 RequestedClaimTypes 根据在中定义的用户声明类型填充声明中的声明 IdentityResource 。如果请求的范围是API资源,则将 RequestedClaimTypes 根据 ApiResource 和/或中定义的用户声明类型填充声明中的声明 Scope

IsActiveContext

对要确定的请求建模是当前允许用户获取令牌。它包含以下属性:

Subject

ClaimsPrincipal 模型的用户。

Client

Client 用于正被请求的权利要求。

Caller

正在请求声明的上下文的标识符(例如,身份令牌,访问令牌或用户信息端点)。常量 IdentityServerConstants.ProfileDataCallers 包含不同的常量值。

IsActive

指示是否允许用户获取令牌的标志。预计这将由自定义 IProfileService 实现分配。

IdentityServer交互服务

IIdentityServerInteractionService 接口旨在提供用户界面用于与IdentityServer通信的服务,主要涉及用户交互。它可以从依赖注入系统获得,通常作为构造函数参数注入到IdentityServer的用户界面的MVC控制器中。

IIdentityServerInteractionService的API

GetAuthorizationContextAsync

返回 AuthorizationRequest 基于 returnUrl 传递给登录或同意页面。

IsValidReturnUrl

指示 returnUrl 登录或同意后是否为重定向的有效URL。

GetErrorContextAsync

返回 ErrorMessage 基于 errorId 传递给错误页面。

GetLogoutContextAsync

返回 LogoutRequest 基于 logoutId 传递给注销页面。

CreateLogoutContextAsync

logoutId 如果目前没有一个用于创建。这将创建一个cookie,捕获注销所需的所有当前状态,并 logoutId 标识该cookie。这通常在没有当前时使用, logoutId 并且注销页面必须捕获当前用户在重定向到外部身份提供程序以进行注销之前注销所需的状态。新创建的 logoutId 需要在注销时往返外部身份提供商,然后在注销回调页面上使用,就像在普通注销页面上一样。

GrantConsentAsync

接受a ConsentResponse 以通知IdentityServer用户同意某个特定用户 AuthorizationRequest

GetAllUserConsentsAsync

返回 Consent 用户的集合。

RevokeUserConsentAsync

撤消用户对用户的所有同意和授权。

RevokeTokensForCurrentSessionAsync

撤消用户在当前会话期间签署的客户的所有同意和授权。

AuthorizationRequest

ClientId

发起请求的客户端标识符。

RedirectUri

成功授权后将用户重定向到的URI。

DisplayMode

显示模式从授权请求传递。

UiLocales

从授权请求传递的UI语言环境。

外部身份提供者请求。这用于绕过家庭领域发现(HRD)。这是通过“idp:”前缀提供给 acr_values 授权请求的参数。

Tenant

租客要求。这是通过“tenant:”前缀提供给 acr_values 授权请求的参数。

LoginHint

用户将用于登录的预期用户名。这是通过 login_hint 授权请求上的参数从客户端请求的。

PromptMode

从授权请求请求的提示模式。

AcrValues

从授权请求传递的acr值。

ScopesRequested

授权请求中请求的范围。

Parameters

整个参数集合传递给授权请求。

的ErrorMessage

DisplayMode

显示模式从授权请求传递。

UiLocales

从授权请求传递的UI语言环境。

Error

错误代码。

RequestId

每请求标识符。这可用于向最终用户显示,并可用于诊断。

LogoutRequest

ClientId

发起请求的客户端标识符。

PostLogoutRedirectUri

用户在注销后将其重定向到的URL。

SessionId

用户当前的会话ID。

SignOutIFrameUrl

要在 <iframe> 注销页面上呈现以启用单点注销的URL 。

Parameters

整个参数集合传递给结束会话端点。

ShowSignoutPrompt

指示是否应根据传递到结束会话端点的参数提示用户注销。

ConsentResponse

ScopesConsented

用户同意的范围集合。

RememberConsent

指示是否持久保留用户同意的标志。

SubjectId

授予同意的主题ID。

ClientId

同意的客户端标识符。

Scopes

范围的集合同意。

CreationTime

获得同意的日期和时间。

Expiration

同意过期的日期和时间。

IdentityServer选项

IssuerUri

设置将在发现文档和已颁发的JWT令牌中显示的颁发者名称。建议不要设置此属性,该属性从客户端使用的主机名中推断颁发者名称。

RequireCspFrameSrcForSignout

如果设置,将要求frame-src CSP标头在结束会话回调端点上发出,该端点向客户端呈现iframe以进行前端通道注销通知。默认为true。

允许配置是否应将哪些事件提交到已注册的事件接收器。有关活动的更多信息,请参见此处。

InputLengthRestrictions

允许设置各种协议参数的长度限制,如客户端ID,范围,重定向URI等。

LoginUrl LogoutUrl ConsentUrl ErrorUrl

设置登录,注销,同意和错误页面的URL。

CookieMessageThreshold

IdentityServer和某些UI页面之间的某些交互需要cookie来传递状态和上下文(上面的任何具有可配置“message id”参数的页面)。由于浏览器对cookie的数量及其大小有限制,因此该设置用于防止创建过多的cookie。该值设置将创建的任何类型的消息cookie的最大数量。一旦达到限制,将清除最早的消息cookie。这有效地表示用户在使用IdentityServer时可以打开多少个选项卡。

这些设置仅在启动时在服务配置中启用了相应的缓存时才适用。

ClientStoreExpiration

从客户端存储加载的客户端配置的缓存持续时间。

PreflightCacheDuration

Nullable <TimeSpan>指示在预检Access-Control-Max-Age响应头中使用的值。默认为null,表示未在响应上设置缓存标头。

CSP(内容安全策略)

在适当的情况下,IdentityServer会为某些响应发出CSP标头。

Level

要使用的CSP级别。默认情况下使用CSP级别2,但如果必须支持旧浏览器,则将其更改 CspLevel.One 为容纳它们。

实体框架支持

为IdentityServer中的配置和操作数据扩展点提供了基于EntityFramework的实现。EntityFramework的使用允许任何EF支持的数据库与此库一起使用。

这个库的repo位于这里,NuGet包在这里。

此库提供的功能分为两个主要区域:配置存储和操作存储支持。根据托管应用程序的需要,这两个不同的区域可以独立使用或一起使用。

配置存储支持客户端,资源和CORS设置

如果希望从EF支持的数据库加载客户端,标识资源,API资源或CORS数据(而不是使用内存配置),则可以使用配置存储。此支持提供了 IClientStore IResourceStore ,以及 ICorsPolicyService 可扩展性点的实现。这些实现使用一个 DbContext 被调用的类 ConfigurationDbContext 来为数据库中的表建模。

要使用配置存储支持,请 AddConfigurationStore 在调用后使用扩展方法 AddIdentityServer

public IServiceProvider ConfigureServices(IServiceCollection services)
{
  const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.EntityFramework-2.0.0;trusted_connection=yes;";
  var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

  services.AddIdentityServer()
      // this adds the config data from DB (clients, resources, CORS)
      .AddConfigurationStore(options =>
      {
          options.ConfigureDbContext = builder =>
              builder.UseSqlServer(connectionString,
                  sql => sql.MigrationsAssembly(migrationsAssembly));
      });
}

要配置配置存储,请使用 ConfigurationStoreOptions 传递给配置回调的options对象。

ConfigurationStoreOptions

此选项类包含用于控制配置存储的属性 ConfigurationDbContext

ConfigureDbContext

Action<DbContextOptionsBuilder> 用作回调的类型委托来配置底层证券 ConfigurationDbContext ConfigurationDbContext 如果直接使用EF,代理可以以相同的方式配置 AddDbContext ,这允许使用任何EF支持的数据库。

DefaultSchema

允许为中的所有表设置默认数据库模式名称 ConfigurationDbContext

操作存储支持授权授予,同意和令牌(刷新和引用)

如果希望从EF支持的数据库(而不是默认的内存数据库)加载授权授予,同意和令牌(刷新和引用),则可以使用操作存储。此支持提供了 IPersistedGrantStore 可扩展性点的实现。该实现使用一个 DbContext 被调用的类 PersistedGrantDbContext 来为数据库中的表建模。

要使用操作商店支持,请 AddOperationalStore 在调用后使用扩展方法 AddIdentityServer

public IServiceProvider ConfigureServices(IServiceCollection services)
{
  const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.EntityFramework-2.0.0;trusted_connection=yes;";
  var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

  services.AddIdentityServer()
      // this adds the operational data from DB (codes, tokens, consents)
      .AddOperationalStore(options =>
      {
          options.ConfigureDbContext = builder =>
              builder.UseSqlServer(connectionString,
                  sql => sql.MigrationsAssembly(migrationsAssembly));

          // this enables automatic token cleanup. this is optional.
          options.EnableTokenCleanup = true;
          options.TokenCleanupInterval = 30; // interval in seconds
      });
}

要配置操作存储,请使用 OperationalStoreOptions 传递给配置回调的options对象。

OperationalStoreOptions

此选项类包含用于控制操作存储的属性 PersistedGrantDbContext

ConfigureDbContext

Action<DbContextOptionsBuilder> 用作回调的类型委托来配置底层证券 PersistedGrantDbContext PersistedGrantDbContext 如果直接使用EF,代理可以以相同的方式配置 AddDbContext ,这允许使用任何EF支持的数据库。

DefaultSchema

允许为中的所有表设置默认数据库模式名称 PersistedGrantDbContext

EnableTokenCleanup

指示是否将从数据库中自动清除过时条目。默认是 false

TokenCleanupInterval

令牌清理间隔(以秒为单位)。默认值为3600(1小时)。

跨不同版本的IdentityServer的数据库创建和架构更改

跨不同版本的IdentityServer(以及EF支持)很可能会更改数据库架构以适应新的和不断变化的功能。

我们不为创建数据库或将数据从一个版本迁移到另一个版本提供任何支持。您需要以组织认为合适的任何方式管理数据库创建,架构更改和数据迁移。

使用EF迁移是一种可行的方法。如果您确实希望使用迁移,请参阅EF快速入门以获取有关如何入门的示例,或参阅有关EF迁移的Microsoft 文档。

我们还为当前版本的数据库模式发布了示例SQL脚本。

ASP.NET身份支持

提供了基于ASP.NET身份的实现,用于管理IdentityServer用户的身份数据库。此实现实现IdentityServer中的扩展点,以便为用户加载身份数据以将声明发送到令牌。

这个支持的回购位于此处,NuGet包就在这里。

要使用此库,请正常配置ASP.NET标识。然后 AddAspNetIdentity 在调用后使用扩展方法 AddIdentityServer

public void ConfigureServices(IServiceCollection services)
{
  services.AddIdentity<ApplicationUser, IdentityRole>()
      .AddEntityFrameworkStores<ApplicationDbContext>()
      .AddDefaultTokenProviders();

  services.AddIdentityServer()
      .AddAspNetIdentity<ApplicationUser>();
}

AddAspNetIdentity 需要作为通用参数来为您的用户建模ASP.NET身份的类(以及传递给 AddIdentity ASP.NET身份的同一个用户)。这将配置IdentityServer使用的ASP.NET身份的实现 IUserClaimsPrincipalFactory IResourceOwnerPasswordValidator IProfileService 。它还配置了一些用于IdentityServer的ASP.NET Identity选项(例如要使用的声明类型和身份验证cookie设置)。

以下是一些在线,远程和课堂培训选项,以了解有关ASP.NET核心身份和IdentityServer4的更多信息。

现代应用程序的身份和访问控制(使用ASP.NET Core 2和IdentityServer4)

这是我们为期三天的旗舰课程(包括广泛的实践实验室),我们作为会议,现场和远程的一部分提供。

可在此处找到公共培训的议程和日期,请 联系我们参加私人研讨会。

PluralSight课程

PluralSight有一些关于身份,ASP.NET Core和IdentityServer的好课程。

使用OpenID和OAuth2保护Angular应用程序

ASP.NET核心身份管理手册

ASP.NET Core和OAuth入门

使用OAuth2和OpenID Connect保护ASP.NET Core

了解ASP.NET核心安全性(使用令牌服务进行集中身份验证)

OAuth2,OpenID Connect和JSON Web令牌(JWT)简介

Web API v2安全性

使用OAuth保护ASP.NET API

Angular和ASP.NET的OAuth2和OpenID Connect策略

可以运行IdentityServer4的平台

优化令牌的大小

身份与权限

Bootstraping OpenID Connect:Discovery

使用WS-Federation支持扩展IdentityServer4

宣布IdentityServer4 RC1

IdentityServer入门4

使用WS-Federation的IdentityServer 4 SharePoint集成

IdentityServer和Swagger

什么是新帖子

IdentityServer4 v2中的新功能:负载均衡器或反向代理后面的简化配置

IdentityServer4中的新功能:没有秘密的客户端

IdentityServer4中的新功能:默认范围

IdentityServer4中的新功能:支持扩展授权

IdentityServer4中的新功能:资源所有者密码验证

IdentityServer4中的新功能:基于资源的配置

IdentityServer4中的新功能:事件

使用ui_locales进行IdentityServer本地化

在IdentityServer4服务中自行发布IdentityServer4令牌

ASP.NET团队博客上的IdentityServer4

Angular2 OpenID使用IdentityServer4连接隐式流

使用IdentityServer4和OpenID连接隐式流的完整服务器注销

IdentityServer4,ASP.NET Identity,Web API和Angular在一个项目中

使用IdentityServer 4保护您的.NETCore Web应用程序

带有自定义UserRepository的ASP.NET Core IdentityServer4资源所有者密码流

使用IdentityServer4 OpenID Connect Hybrid Flow使用Angular保护ASP.NET Core MVC

将外部Microsoft登录添加到IdentityServer4

使用IdentityServer4和Twilio实现双因素身份验证

2018年

[17/01 NDC London] - ASP.NET Core v2上的IdentityServer v2 - 一个更新

[17/01 NDC伦敦] - 实施Web应用程序和API授权(又名PolicyServer公告)

[17/01 DotNetRocks] - DotNetRocks上的IdentityServer和PolicyServer

2017年

[14/09 Microsoft Learning] - IdentityServer for ASP.NET Core简介 - Brock Allen

[14/06 NDC Oslo] - 实施Web应用程序和API的授权

[22/02 NDC Mini Copenhagen] - IdentityServer4:ASP.NET核心的新增和改进 - Dominick Baier

[02/02 DotNetRocks] - DotNetRocks上的IdentityServer4

[16/01 NDC伦敦] - IdentityServer4:ASP.NET核心的新增功能和改进版

[16/01 NDC London] - 为基于令牌的架构构建JavaScript和移动/本机客户端

.NET身份和IdentityServer Channel9采访的历史

本机和移动应用程序的身份验证和安全API访问 - Dominick Baier

ASP.NET身份3 - Brock Allen

IdentityServer3简介 - Brock Allen

2015年

保护Web API - 模式和反模式 - Dominick Baier

现代JavaScript Web应用程序中的身份验证和授权 - 它有多难? - 布洛克艾伦

使用OpenID Connect和OAuth 2统一移动,Web和桌面的身份验证和委托API访问 - Dominick Baier