IdentityServer4中文文档

欢迎IdentityServer4

IdentityServer4是ASP.NET Core 2的OpenID Connect和OAuth 2.0框架。

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

认证即服务

适用于所有应用程序(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实现以下规范:

ID连接

  • 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 的自述文件。

注意

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

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

创建MVC客户端

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

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

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)。我们设置为DefaultChallengeSchemeto,"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();

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

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.Typedt>
       <dd>@claim.Valuedd>
  }
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 GetIdentityResources()
{
  return new List
  {
      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 GetClients()
{
  return new List
  {
      // 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
          {
              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。由于我们正在使用AddTestUsersTestUserProfileService默认使用。您可以在此处检查源代码 以查看其工作原理。

添加对外部认证的支持

接下来,我们将添加对外部认证的支持。这非常简单,因为您真正需要的是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。这是通过添加该代码段完成ConfigureServicesStartup

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_accessapi1范围,并设置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命名空间中的扩展方法。

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

access token

@await ViewContext.HttpContext.GetTokenAsync("access_token")


refresh token

@await ViewContext.HttpContext.GetTokenAsync("refresh_token")

要使用访问令牌访问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.AspNetIdentityNuGet包。这取决于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(options =>
      options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

  services.AddIdentity()
      .AddEntityFrameworkStores()
      .AddDefaultTokenProviders();

  // Add application services.
  services.AddTransient();

  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();
}

注意

在使用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

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

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

在的package.json一个补充dependencyoidc-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。它还将包含
 

app.js

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

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提供getUserAPI以了解用户是否登录到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");
  }
});

接下来,我们要实现的loginapilogout功能。在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页面。添加此代码以完成登录过程:




 
 


 
 

客户注册加入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文件中配置了这些工具。如果是,您可以跳到下一部分。

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



它应该看起来像这样:

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

配置商店

接下来的步骤是,以取代当前呼叫AddInMemoryClientsAddInMemoryIdentityResourcesAddInMemoryApiResourcesConfigureServices在方法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;

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

传递给这些API的“构建器”回调函数是EF机制,允许您为这两个存储中的每一个配置DbContextOptionsBuilderfor 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().CreateScope())
  {
      serviceScope.ServiceProvider.GetRequiredService().Database.Migrate();

      var context = serviceScope.ServiceProvider.GetRequiredService();
      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或引用。

    • AddDeveloperSigningCredential

      在启动时创建临时密钥材料。当您没有要使用的证书时,这仅适用于dev。生成的密钥将保留到文件系统,以便在服务器重新启动之间保持稳定(可以通过传递禁用false)。这解决了客户端/ api元数据缓存在开发期间不同步时的问题。

    • AddValidationKey

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

内存配置存储

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

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

    • AddInMemoryClients

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

    • AddInMemoryIdentityResources

      IResourceStore根据IdentityResource配置对象的内存中集合注册实现。

    • AddInMemoryApiResources

      IResourceStore根据ApiResource配置对象的内存中集合注册实现。

测试商店

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

    • AddTestUsers

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

附加服务

    • AddExtensionGrantValidator

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

    • AddSecretParser

      添加ISecretParser用于解析客户端或API资源凭据的实现。

    • AddSecretValidator

      添加ISecretValidator用于针对凭证存储验证客户端或API资源凭证的实现。

    • AddResourceOwnerValidator

      添加IResourceOwnerPasswordValidator用于验证资源所有者密码凭据授予类型的用户凭据的实现。

    • AddProfileService

      添加IProfileService用于连接到自定义用户配置文件存储的实现。的DefaultProfileService类提供了依赖于验证cookie作为权利要求中的用于在令牌发行的唯一来源的默认实现。

    • AddAuthorizeInteractionResponseGenerator

      添加IAuthorizeInteractionResponseGenerator实现以在授权端点处定制逻辑,以便在必须向用户显示错误,登录,同意或任何其他自定义页面的UI时。本AuthorizeInteractionResponseGenerator类提供了一个默认的实现,因此考虑从这个现有的类派生如果需要增强现有的行为。

    • AddCustomAuthorizeRequestValidator

      添加ICustomAuthorizeRequestValidator实现以在授权端点处自定义请求参数验证。

    • AddCustomTokenRequestValidator

      添加ICustomTokenRequestValidator实现以在令牌端点处自定义请求参数验证。

    • AddRedirectUriValidator

      添加IRedirectUriValidator实现以自定义重定向URI验证。

    • AddAppAuthRedirectUriValidator

      添加符合重定向URI验证器的“AppAuth”(适用于Native Apps的OAuth 2.0)(进行严格验证,但也允许带有随机端口的http://127.0.0.1)。

    • AddJwtBearerClientAuthentication

      使用JWT承载断言添加对客户端身份验证的支持。

缓存

IdentityServer经常使用客户端和资源配置数据。如果从数据库或其他外部存储加载此数据,则频繁重新加载相同数据可能会很昂贵。

    • AddInMemoryCaching

      要使用下面描述的任何缓存,ICache必须在DI中注册实现。此API注册了ICache基于ASP.NET Core的默认内存实现MemoryCache

    • AddClientStoreCache

      注册IClientStore装饰器实现,该实现将维护Client配置对象的内存缓存。缓存持续时间可在Caching配置选项上配置IdentityServerOptions

    • AddResourceStoreCache

      注册一个IResourceStore装饰器实现,它将维护内存缓存IdentityResourceApiResource配置对象。缓存持续时间可在Caching配置选项上配置IdentityServerOptions

    • AddCorsPolicyCache

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

可以进一步自定义缓存:

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

ICache本身的默认实现依赖于.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 GetIdentityResources()
{
  return new List
  {
      new IdentityResources.OpenId()
  };
}

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

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

定义自定义标识资源

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

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

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

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

定义API资源

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

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

public static IEnumerable 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通过AddInMemoryClientsextensions方法连接内存存储。

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

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

public class Clients
{
  public static IEnumerable Get()
  {
      return new List
      {
          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类(DefaultCookieAuthenticationSchemeExternalCookieAuthenticationScheme)中获取它们的名称。

我们只公开这些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在内部调用两个AddAuthenticationAddCookie使用自定义方案(通过常量IdentityServerConstants.DefaultCookieAuthenticationScheme),因此要覆盖它们,您必须在之后进行相同的调用AddIdentityServer

登录用户界面和身份管理系统

IdentityServer不为用户身份验证提供任何用户界面或用户数据库。这些是您希望自己提供或发展的东西。

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

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

登录工作流程

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

注意

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

登录上下文

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

发行cookie和声明

HttpContextASP.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中存储状态。您可以通过ISecureDataFormatOpenIdConnectOptions上实现和配置它来自行实现。

幸运的是,IdentityServer为您提供了一个实现,由IDistributedCacheDI容器中注册的实现(例如标准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服务器

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

使用红隼

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

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

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

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

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

services.Configure(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中的“已注销”页面必须呈现