IdentityServer4 中文文档 -9- (快速入门)使用客户端凭证保护API


快速入门展示了使用 IdentityServer 保护 API 的最基础的场景。

在这个场景中,我们定义一个 API,同时定义一个 想要访问这个 API 的 客户端。客户端将从 IdentityServer 请求获得一个访问令牌,然后用这个令牌来获得 API 的访问权限。

定义 API

范围(Scopes)用来定义系统中你想要保护的资源,比如 API。

由于当前演练中我们使用的是内存配置 —— 添加一个 API,你需要做的只是创建一个 ApiResource 类型的实例,并为它设置合适的属性。

向你的项目中添加一份文件(比如:Config.cs),然后添加如下代码:

  1. public static IEnumerable<ApiResource> GetApiResources()
  2. {
  3. return new List<ApiResource>
  4. {
  5. new ApiResource("api1", "我的 API")
  6. };
  7. }

定义 客户端

下一步是定义能够访问上述 API 的客户端。

在该场景中,客户端不会有用户参与交互,并且将使用 IdentityServer 中所谓的客户端密码(Client Secret)来认证。添加如下代码到你的配置中:

  1. public static IEnumerable<Client> GetClients()
  2. {
  3. return new List<Client>
  4. {
  5. new Client
  6. {
  7. ClientId = "client",
  8. // 没有交互性用户,使用 clientid/secret 实现认证。
  9. AllowedGrantTypes = GrantTypes.ClientCredentials,
  10. // 用于认证的密码
  11. ClientSecrets =
  12. {
  13. new Secret("secret".Sha256())
  14. },
  15. // 客户端有权访问的范围(Scopes)
  16. AllowedScopes = { "api1" }
  17. }
  18. };
  19. }

配置 IdentityServer

为了让 IdentityServer 使用你的 Scopes 和 客户端 定义,你需要向 ConfigureServices 方法中添加一些代码。你可以使用便捷的扩展方法来实现 —— 它们在幕后会添加相关的存储和数据到 DI 系统中:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // 使用内存存储,密钥,客户端和资源来配置身份服务器。
  4. services.AddIdentityServer()
  5. .AddTemporarySigningCredential()
  6. .AddInMemoryApiResources(Config.GetApiResources())
  7. .AddInMemoryClients(Config.GetClients());
  8. }

现在,如果你运行服务器并将浏览器导航到 http://localhost:5000/.well-known/openid-configuration,你应该看能到所谓的 发现文档。你的客户端和 API 将使用这些信息来下载所需要的配置数据。

9、使用客户端凭证保护API - 图1

添加 API

下一步,向你的解决方案中添加 API。

你可以使用 ASP.NET Core Web API 模板,或者添加 Microsoft.AspNetCore.Mvc 程序包到你的项目中。再一次建议你像之前一样,控制所使用的端口并使用与之前配置 Kestrel 和启动资料相同的技术。该演练假设你已经将你的 API 配置为运行于 http://localhost:5001 之上。

控制器

添加一个新的控制器到你的 API 项目中:

  1. [Route("identity")]
  2. [Authorize]
  3. public class IdentityController : ControllerBase
  4. {
  5. [HttpGet()]
  6. public IActionResult Get()
  7. {
  8. return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
  9. }
  10. }

这个控制器将在后面被用于测试授权需求,同时通过API的眼睛(浏览工具)来可视化身份信息。

配置

接下来,添加认证中间件到 API 宿主。该中间件的主要工作是:

  • 验证输入的令牌以确保它来自可信任的发布者(IdentityServer);
  • 验证令牌是否可用于该 api(也就是 Scope)。

IdentityServer4.AccessTokenValidation NuGet 程序包添加到你的 API 项目:

9、使用客户端凭证保护API - 图2

你还需要添加中间件到你的 HTTP 管道中 —— 必须在添加 MVC 之前添加,比如:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  2. {
  3. loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  4. loggerFactory.AddDebug();
  5. app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions()
  6. {
  7. Authority = "http://localhost:5000",
  8. RequireHttpsMetadata = false,
  9. ApiName = "api1"
  10. });
  11. app.UseMvc();
  12. }

如果你使用浏览器导航到上述控制器(http://localhost:5001/identity),你应该收到返回的 401 状态码。这意味着你的 API 要求提供证书。

这样一来, API 就是受 IdentityServer 保护的了。

创建客户端

最后一个步骤是编写一个客户端来请求访问令牌,然后使用这个令牌来访问 API。为此你需要为你的解决方案添加一个控制台应用程序。

IdentityServer 上的令牌端点实现了 OAuth 2.0 协议,你应该使用合法的 HTTP 来访问它。然而,我们有一个叫做 IdentityModel 的客户端库,它将协议交互封装到了一个易于使用的 API 里面。

添加 IdentityModel NuGet 程序包到你的客户端项目中。

9、使用客户端凭证保护API - 图3

IdentityModel 包含了一个用于 发现端点 的客户端库。这样一来你只需要知道 IdentityServer 的基础地址 —— 实际的端点地址可以从元数据中读取:

  1. // 从元数据中发现端口
  2. var disco = await DiscoveryClient.GetAsync("http://localhost:5000");

接着你可以使用 TokenClient 类型来请求令牌。为了创建一个该类型的实例,你需要传入令牌端点地址、客户端id和密码。

然后你可以使用 RequestClientCredentialsAsync 方法来为你的目标 API 请求一个令牌:

  1. // 请求以获得令牌
  2. var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
  3. var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
  4. if (tokenResponse.IsError)
  5. {
  6. Console.WriteLine(tokenResponse.Error);
  7. return;
  8. }
  9. Console.WriteLine(tokenResponse.Json);

注意:从控制台中复制和粘贴访问令牌到 jwt.io 以检查令牌的合法性。

最后是调用 API。

为了发送访问令牌到 API,你一般要使用 HTTP 授权 header。这可以通过 SetBearerToken 扩展方法来实现:

  1. // 调用API
  2. var client = new HttpClient();
  3. client.SetBearerToken(tokenResponse.AccessToken);
  4. var response = await client.GetAsync("http://localhost:5001/identity");
  5. if (!response.IsSuccessStatusCode)
  6. {
  7. Console.WriteLine(response.StatusCode);
  8. }
  9. else
  10. {
  11. var content = await response.Content.ReadAsStringAsync();
  12. Console.WriteLine(JArray.Parse(content));
  13. }

最终输出看起来应该是这样的:

9、使用客户端凭证保护API - 图4

注意:默认情况下访问令牌将包含 scope 身份信息,生命周期(nbf 和 exp),客户端 ID(client_id) 和 发行者名称(iss)。

进一步实践

当前演练目前主要关注的是成功的步骤:

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

你现在可以尝试引发一些错误来学习系统的相关行为,比如:

  • 尝试在 IdentityServer 未运行时(unavailable)连接它
  • 尝试使用一个非法的客户端id或密码来请求令牌
  • 尝试在请求令牌的过程中请求一个非法的 scope
  • 尝试在 API 未运行时(unavailable)调用它
  • 不向 API 发送令牌
  • 配置 API 为需要不同于令牌中的 scope