一. 概述
OAuth 2.0 资源 (web API) 所有者密码授权, 允许客户端 (Client 项目) 向令牌服务 (IdentityServer 项目) 发送用户名和密码, 并获取代表该用户的访问令牌. 在官方文档中讲到: 规范通常建议不要使用 "资源所有者密码授权". 当用户进行身份验证并请求访问令牌时, 使用一个交互式 OpenID Connect 流程通常要好得多(下篇再了解).
本篇介绍 "资源所有者密码授权" 是因为这种授权允许我们快速启动 IdentityServer. 开源地址: GitHub
下面示例与官方示例有点区别, 该示例使用了 Identity 密码保护 API. 关于 ASP.NET core Identity 的了解实现, 查看之前章节或官方文档. 示例中分别是 IdentityServer 令牌项目, API 资源项目, Client 访问项目. 与上篇相比一样, 还是三个项目, 区别在于:
(1) IdentityServer 令牌项目换成了含有 ASP.NET core Identity 的 MVC 项目.
(2) API 资源项目没有变动.
(3) Client 访问项目使用了用户名和密码访问受保护的 API.
二. IdentityServer 项目
IdentityServer 令牌项目是包含了 Identity 功能(安装: Install-Package IdentityServer4), 在项目中, 添加了 Config.cs 类和 Startup.cs 中加入了 IdentityServer 的启动配置. 下面是 MVC 项目目录结构:
(1) 添加用户
IdentityServer 类库中自带 TestUser 测试类, 是 DTO 数据传输对象, 存储用户及其声明 (claims).TestUser 是用于测试中的内存(In-memory) 用户对象. 在正式环境下, 获取数据库中的用户表(User), 需要结合 IdentityServer 的 IResourceOwnerPasswordValidator 接口(不再本篇讲述中). 下面通过在 config.cs 类中添加 GetUsers 方法获取用户密码, 存储在 TestUser 数据传输对象中.
- /// <summary>
- /// 获取用户, 这些用户可以访问受密码保护的 API
- /// </summary>
- /// <param name="provider"></param>
- /// <returns></returns>
- public static List<TestUser> GetUsers(ServiceProvider provider)
- {
- var webAppIdentityDemoUser = provider.GetRequiredService<UserManager<WebAppIdentityDemoUser>>();
- IList<WebAppIdentityDemoUser> users = null;
- // 获取 Identity 的 User 表用户, 条件是属于 Administrator 角色的用户
- users = webAppIdentityDemoUser.GetUsersInRoleAsync("Administrator").Result;
- List<TestUser> testUserList = new List<TestUser>();
- foreach (WebAppIdentityDemoUser user in users)
- {
- testUserList.Add(new TestUser() { SubjectId = user.Id.ToString(), Username = user.UserName, Password = user.PasswordHash });
- }
- return testUserList;
- }
(2) 然后在 Startup 类的 ConfigureServices 方法中使用 IdentityServer 注入测试用户:
- ServiceProvider provider = services.BuildServiceProvider();
- var builder = services.AddIdentityServer()
- .AddInMemoryIdentityResources(Config.GetIdentityResources())
- .AddInMemoryApiResources(Config.GetApis())
- .AddInMemoryClients(Config.GetClients())
- .AddTestUsers(Config.GetUsers(provider));
(3) 定义客户端, 使用密码授予访问此 API(资源范围: api1)
在 config.cs 类中, 定义客户端, 通过修改 AllowedGrantTypes 枚举来简单地向现有客户端添加对授权类型的支持, 将以下代码添加到客户端配置中, 里面支持二个 Client 授权类型, 分别是 ClientCredentials 使用凭证来访问令牌和 ResourceOwnerPassword 使用密码来访问令牌.
- 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" }
- },
- // resource owner password grant client
- new Client
- {
- ClientId = "ro.client",
- AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
- ClientSecrets =
- {
- new Secret("secret".Sha256())
- },
- AllowedScopes = { "api1" }
- }
- };
- }
三. Client 项目
该 Client 项目类似于上篇介绍的 Client 项目, 该项目名为 ResourceOwnerClient, 该 Client 将收集用户名和密码, 并在令牌请求期间, 将其发送到 IdentityServer 令牌服务(WebAppIdentityDemo 项目)
- // request token 请求令牌
- var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
- {
- Address = disco.TokenEndpoint,
- ClientId = "ro.client",
- ClientSecret = "secret",
- UserName = "924964690@qq.com",
- Password = "AQAAAAEAACcQAAAAEH4Xhui5BByq6d8VS5Z+S2o2SnlkyrP5pN9CmMpgJ4QiIVrt7lBLzDlEWa6AdlpxpA==",
- Scope = "api1"
- });
- if (tokenResponse.IsError)
- {
- Console.WriteLine(tokenResponse.Error);
- return;
- }
- Console.WriteLine(tokenResponse.JSON);
- Console.WriteLine("\n\n");
最后测试, 先启动 WebAppIdentityDemo 项目程序, 再启动 API 程序, 最后启动 Client 客户端来访问 API, 通过下图可以了解到:(1)客户端请求使用 "用户名和和密码" 访问令牌 (token) 成功, (2) 客户端使用令牌 (AccessToken) 来访问受密码保护的 Web API 接口成功.
参考文献
使用密码保护 API
来源: https://www.cnblogs.com/MrHSR/p/10709099.html