一. 概述
本篇开始进入 IS4 实战学习, 从第一个示例开始, 该示例是 "使用客户端凭据保护 API", 这是使用 IdentityServer 保护 API 的最基本场景. 该示例涉及到三个项目包括: IdentityServer 项目, API 项目, Client 项目, 都有自己的宿主, 为了方便开发, 放在了一个解决方案下 (Quickstart.sln), 三个项目的分工如下:
(1) IdentityServer 项目是包含基本的 IdentityServer 设置的 ASP.NET Core 应用程序, 是令牌端点.
(2) API 项目是 web API, 是要保护的资源.
(3) Client 项目是客户端用户, 用来访问 Web API.
最后客户端 Client 项目请求获取 IdentityServer 上的访问令牌. 作为客户端 Client 和 IdentityServer 都知道 secret 密钥, Client 将使用令牌访问 Web API. 开源地址 GitHub
二. 创建 IdentityServer 项目
创建一个 ASP.NET Core Web(或空) 模板. 项目名为 IdentityServer, 解决方案为 Quickstart. 是一个包含基本 IdentityServer 设置的 ASP.NET Core 应用程序. 该项目使用的协议是 http, 当在 Kestrel 上运行时, 端口设置为 5000 或在 IISExpress 上的随机端口.
首次启动时, IdentityServer 将为您创建一个开发人员签名密钥, 它是一个名为的文件 tempkey.rsa. 您不必将该文件检入源代码管理中, 如果该文件不存在, 将重新创建该文件. 项目最终目录结构如下所示:
下面进行说明, 以及用序号来表示开发实现步骤:
2.1 安装: Install-Package IdentityServer4
2.2 新增 Config.cs 文件, 该文件是 IdentityServer 资源和客户端配置文件. 在该文件中定义 API 资源, 以及定义客户端 (可以访问此 API 的客户端)
- /// <summary>
- /// 定义 API 资源, 要保护的资源
- /// </summary>
- /// <returns></returns>
- public static IEnumerable<ApiResource> GetApis()
- {
- return new List<ApiResource>
- {
- new ApiResource("api1", "My API")
- };
- }
- /// <summary>
- /// 定义客户端, 可以访问此 API 的客户端
- /// </summary>
- /// <returns></returns>
- 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())
- },
- // 客户端允许访问的范围
- AllowedScopes = { "api1" }
- }
- };
- }
2.3 Startup 配置
- /// <summary>
- /// 配置 IdentityServer, 加载 API 资源和客户端
- /// </summary>
- /// <param name="services"></param>
- public void ConfigureServices(IServiceCollection services)
- {
- // uncomment, if you wan to add an MVC-based UI
- //services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
- // 添加 AddIdentityServer
- var builder = services.AddIdentityServer()
- // 添加内存的 Identity 资源
- .AddInMemoryIdentityResources(Config.GetIdentityResources())
- // 添加 API 资源
- .AddInMemoryApiResources(Config.GetApis())
- // 添加 clinet
- .AddInMemoryClients(Config.GetClients());
- if (Environment.IsDevelopment())
- {
- // 开发环境下使用临时签名凭据
- builder.AddDeveloperSigningCredential();
- }
- else
- {
- throw new Exception("need to configure key material");
- }
- }
- public void Configure(IApplicationBuilder App)
- {
- if (Environment.IsDevelopment())
- {
- App.UseDeveloperExceptionPage();
- }
- // uncomment if you want to support static files
- //App.UseStaticFiles();
- App.UseIdentityServer();
- // uncomment, if you wan to add an MVC-based UI
- //App.UseMvcWithDefaultRoute();
- }
运行服务器并浏览浏览器 http://localhost:5000/.well-known/openid-configuration, 客户端和 API 将使用它来下载必要的配置数据. 下面是截取的部分配置数据:
三. 创建 API 项目
在解决方案下继续添加 API 项目, 添加 ASP.NET Core Web API(或空) 模板. 将 API 应用程序配置为 http://localhost:5001 运行. 项目最终目录结构如下所示:
(1) 在 API 项目中添加一个新文件夹 Controllers 和一个新控制器 IdentityController
- // 定义路由
- [Route("identity")]
- // 需要授权
- [Authorize]
- public class IdentityController : ControllerBase
- {
- /// <summary>
- /// 测试授权, 获取该用户下声明集合 Claims
- /// </summary>
- /// <returns></returns>
- public IActionResult Get()
- {
- return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
- }
- }
(2) Startup 配置
- public void ConfigureServices(IServiceCollection services)
- {
- // 将最基本的 MVC 服务添加到服务集合中
- services.AddMvcCore()
- // 向基本的 MVC 服务中添加授权
- .AddAuthorization()
- // 向基本的 MVC 服务中添加格式化
- .AddJsonFormatters();
- // 将身份验证服务添加到 DI 服务集合中, 并配置 "Bearer" 为默认方案
- services.AddAuthentication("Bearer")
- // 验证令牌是否有效用于此 API
- .AddJwtBearer("Bearer", options =>
- {
- options.Authority = "http://localhost:5000";
- // 在开发环境禁用, 默认 true
- options.RequireHttpsMetadata = false;
- options.Audience = "api1";
- });
- }
- public void Configure(IApplicationBuilder App)
- {
- // 添加身份验证中间件
- App.UseAuthentication();
- App.UseMvc();
- }
启动程序运行 http://localhost:5001/identity 时返回 401 状态码, 未授权. 意味着 API 需要凭证, 现在受 IdentityServer 保护. 如下所示:
四. 创建 Client 项目
我们通过上面知道, 直接用浏览器来访问 API 是返回 401 状态码未授权, 下面在 Client 项目中使用凭证, 来获得 API 授权访问. 下面是 Client 项目目录结构, 这里 Client 是一个控制台应用程序. 对于客户端可以是任意应用程序, 比如手机端, Web 端, win 服务等等.
在 IdentityServer 的令牌端点实现了 OAuth 2.0 协议, 客户端可以使用原始 HTTP 来访问它. 但是, 我们有一个名为 IdentityModel 的客户端库, 它将协议交互封装在易于使用的 API 中.
3.1 安装: Install-Package IdentityModel
3.2 发现 IdentityServer 端点
IdentityModel 包括用于发现端点的客户端库. 只需要知道 IdentityServer 的基地址 - 可以从元数据中读取实际的端点地址:
- private static async Task Main()
- {
- // discover endpoints from metadata
- var client = new HttpClient();
- var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
- if (disco.IsError)
- {
- // 当停掉 IdentityServer 服务时
- //Error connecting to http://localhost:5000/.well-known/openid-configuration: 由于目标计算机积极拒绝, 无法连接.
- Console.WriteLine(disco.Error);
- return;
- }
- //...
其中 GetDiscoveryDocumentAsync 是属于 IdentityModel 库的, 是对 HttpClient 扩展方法. http://localhost:5000 是 IdentityServer 的基地址.
3.3 请求令牌 Token
在 Mian 方法中继续向 IdentityServer 请求令牌, 访问 api1 资源. 这里的 RequestClientCredentialsTokenAsync 方法也是 HttpClient 扩展方法.
- // request token, 带入需要的 4 个参数, 请求令牌, 返回 TokenResponse
- var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
- {
- //IdentityServer 基地址 http://localhost:5000/connect/token
- Address = disco.TokenEndpoint,
- // 设置客户端标识
- ClientId = "client",
- // 设置密钥
- ClientSecret = "secret",
- // 访问的资源范围
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- {
- Console.WriteLine(tokenResponse.Error);
- return;
- }
- // 打印 token 信息
- Console.WriteLine(tokenResponse.JSON);
- Console.WriteLine("\n\n");
3.4 调用 API
在 Mian 方法中继续向下, 当访问令牌取得后, 开始调用 Web API. 下面将访问令牌发送到 Web API, 通常使用 HTTP Authorization 标头. 这是使用 SetBearerToken 扩展方法完成的, 该方法是 IdentityModel 库的 HttpClient 扩展方法.
- // call API
- var apiClient = new HttpClient();
- // 发送访问令牌
- apiClient.SetBearerToken(tokenResponse.AccessToken);
- // 访问 API, 获取该用户下声明集合 Claims
- var response = await apiClient.GetAsync("http://localhost:5001/identity");
- if (!response.IsSuccessStatusCode)
- {
- Console.WriteLine(response.StatusCode);
- }
- else
- {
- // 输出 claims 名称值 对
- var content = await response.Content.ReadAsStringAsync();
- Console.WriteLine(JArray.Parse(content));
- }
下面开始测试, 先启动 IdentityServer 程序, 再启动 API 程序, 最后启动 Client 客户端来访问 API, 通过下图可以了解到:(1) 客户端请求令牌成功,(2) 客户端使用令牌来访问 API 成功.
如果想进一步尝试激发错误, 来了解系统的行为, 可以错误的去配置如下:
(1) 尝试停掉 IdentityServer 服务程序, 这个已经测试了.
(2) 尝试使用无效的客户端 ID 标识 ClientId = "client",
(3) 尝试在令牌请求期间请求无效范围 Scope = "api1"
(4) 尝试在 API 程序未运行时调用 API
(5) 尝试不要将令牌发送到 API
总结: 通过本篇了解到了 IS4 保护 API 的最基本场景. 流程是首先创建一个 IdentityServer 令牌程序. 接着创建 API 项目, 使用 IdentityServer 令牌程序来保护 API. 最后创建要访问的 Client 项目, 获取访问令牌后再调用 API 方法.
IdentityServer 令牌端对要保护 API 资源做了配置 new ApiResource("api1", "My API")
限制了访问 API 的客户端标识和访问资源范围 ClientId = "client", AllowedScopes = { "api1" } 还有客户端需要的秘钥.
参考文献
使用客户端凭据保护 API
来源: https://www.cnblogs.com/MrHSR/p/10688707.html