在 IdentityServer4 中创建客户端
创建客户端
在创建了 IdentityServer4 服务器之后,我们可以准备从获取一个访问令牌开始。
1. 客户端凭证式验证流
在 OpenID Connect 中,最为简单的验证方式为客户端凭借方式了。我们从这种方式开始。OpenID Connect 是 OAuth 的扩展,我们找一段阮一峰的博客来进行说明。
第四种方式:凭证式
最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。
第一步,A 应用在命令行向 B 发出请求。
https://oauth.b.com/token?
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
上面 URL 中,
grant_type参数等于client_credentials表示采用凭证式,client_id和client_secret用来让 B 确认 A 的身份。第二步,B 网站验证通过以后,直接返回令牌。
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
原文地址:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
2. 服务器端实现
实际的服务端点在上一篇中,通过访问端点 http://localhost:5000/.well-known/openid-configuration 就可以得到,从响应的结果中可以找到如下的一行:
"token_endpoint": "http://localhost:5000/connect/token",
而客户端的标示和密钥则需要我们在服务器端提供。
将上一个项目中的 Config.cs 文件替换为如下内容,代码中硬编码了客户端的标识为 client ,密钥为 secret。
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Models;
using System.Collections.Generic;
namespace IdentityServer
{
public static class Config
{
public static IEnumerable<IdentityResource> Ids =>
new IdentityResource[]
{
new IdentityResources.OpenId()
};
// scopes define the API resources in your system
public static IEnumerable<ApiResource> Apis =>
new List<ApiResource>
{
new ApiResource("api1", "My API")
};
// clients want to access resources (aka scopes)
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
};
}
}
在这里通过 Apis 这个委托提供了 API 资源,通过 Clients 这个委托提供了客户端的凭据。
在启动应用之后,我们可以访问服务器来获取令牌。
3. 获取访问令牌
这里,我们使用 Postman 来完成。
将请求的发送方式修改为 Post
地址设置为:http://localhost:5000/connect/token
请求的 Body ,在 Body 的设置中,首先选中格式:x-www-form-urlencoded,提供如下三个参数:
| KEY | VALUE |
|---|---|
| grant_type | client_credentials |
| client_id | Client |
| client_secret | Secret |
点击 Send 按钮,发送请求之后,就应该可以看到如下的响应内容:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IklWR2VMQ3h5NFJjcWJnbmUxb1JVN3ciLCJ0eXAiOiJhdCtqd3QifQ.eyJuYmYiOjE1ODU4MTQwNzMsImV4cCI6MTU4NTgxNzY3MywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiYXBpMSIsImNsaWVudF9pZCI6ImNsaWVudCIsInNjb3BlIjpbImFwaTEiXX0.R5RNGRM6bVvdNIgdXnD-QK5HK-kHA5hcZ-ltn0K3kLZp9R3BGQeg5qfnQXT4sU2CqPGYIatwbZY3bysQ9krkq5BpWzSzwY7EYPybsP3gty0BUK2QXnEwxsT1boN_cM2Hw9ua4nal3IHB4XJJkMj7jo33S8NtQQyJr26_G1WqlOgvlVfUiPYQWiY9OHPgTAIqrU_4aogoxiC84lHWC5Pf6oX6jxLoAWzKkhl-NdH33gW169xdtkPXp51XpbXhxNujBo7LAVOI-_5ztouuYLShOf5bOt1bunHfeNCv1DPl2rBsfFITjkoltQXVrTSZGLEQgNH_ryBqdoTyM-jWP1HN4g",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "api1"
}
4. 实现自定义的存储
这个 Config 中提供的静态属性只是为了方便进行简单的测试,在 IdentityServer4 内部,定义了验证客户端的接口 IClientStore,如下所示:
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Models;
using System.Threading.Tasks;
namespace IdentityServer4.Stores
{
/// <summary>
/// Retrieval of client configuration
/// </summary>
public interface IClientStore
{
/// <summary>
/// Finds a client by id
/// </summary>
/// <param name="clientId">The client id</param>
/// <returns>The client</returns>
Task<Client> FindClientByIdAsync(string clientId);
}
}
源码地址:https://github.com/IdentityServer/IdentityServer4/blob/master/src/Storage/src/Stores/IClientStore.cs
在 IdentityServer4 的内部,也实现了一个基于内存中集合实现的内存中客户端存储 InMemoryClientStore,代码实现如下:
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace IdentityServer4.Stores
{
/// <summary>
/// In-memory client store
/// </summary>
public class InMemoryClientStore : IClientStore
{
private readonly IEnumerable<Client> _clients;
/// <summary>
/// Initializes a new instance of the <see cref="InMemoryClientStore"/> class.
/// </summary>
/// <param name="clients">The clients.</param>
public InMemoryClientStore(IEnumerable<Client> clients)
{
if (clients.HasDuplicates(m => m.ClientId))
{
throw new ArgumentException("Clients must not contain duplicate ids");
}
_clients = clients;
}
/// <summary>
/// Finds a client by id
/// </summary>
/// <param name="clientId">The client id</param>
/// <returns>
/// The client
/// </returns>
public Task<Client> FindClientByIdAsync(string clientId)
{
var query =
from client in _clients
where client.ClientId == clientId
select client;
return Task.FromResult(query.SingleOrDefault());
}
}
}
你看,我们只需要传一个 Client 的可迭代对象就可以构造这样一个 InMemoryClientStore 对象实例。实际上,我们调用的 AddInMemoryClients 这个扩展方法确实就是这么做的:
/// <summary>
/// Adds the in memory clients.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="clients">The clients.</param>
/// <returns></returns>
public static IIdentityServerBuilder AddInMemoryClients(this IIdentityServerBuilder builder, IEnumerable<Client> clients)
{
builder.Services.AddSingleton(clients);
builder.AddClientStore<InMemoryClientStore>();
var existingCors = builder.Services.Where(x => x.ServiceType == typeof(ICorsPolicyService)).LastOrDefault();
if (existingCors != null &&
existingCors.ImplementationType == typeof(DefaultCorsPolicyService) &&
existingCors.Lifetime == ServiceLifetime.Transient)
{
// if our default is registered, then overwrite with the InMemoryCorsPolicyService
// otherwise don't overwrite with the InMemoryCorsPolicyService, which uses the custom one registered by the host
builder.Services.AddTransient<ICorsPolicyService, InMemoryCorsPolicyService>();
}
return builder;
}
这里使用了依赖注入完成 InMemoryClientStore 的构造注入。
好了,我们自己实现一个自定义的客户端凭据存储。比如 CustomClientStore。
在构造函数中,我们构建了内部存储客户端凭据的集合,由于实现了 IClientStore ,在实现的方法中,提供了检索客户端端的实现,其实这个方法完全从 InMemoryClientStore 复制过来的。
using IdentityServer4.Models;
using IdentityServer4.Stores;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace IdentityServer
{
public class CustomClientStore : IClientStore
{
private readonly IEnumerable<Client> _clients;
public CustomClientStore()
{
_clients = new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
};
}
public Task<Client> FindClientByIdAsync(string clientId)
{
var query =
from client in _clients
where client.ClientId == clientId
select client;
return Task.FromResult(query.SingleOrDefault());
}
}
}
在 .NET Core 中,服务是通过依赖注入的方式被使用的,所以,我们需要注册这个服务。回到 Startup.cs 这个文件,将 ConfigureServices() 方法替换为如下内容。
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
//services.AddControllersWithViews();
services.AddSingleton<IClientStore, CustomClientStore>();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis);
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
}
主要做了两件事:
- 删除了原来的 .AddInMemoryClients(Config.Clients);
- 添加了 services.AddSingleton<IClientStore, CustomClientStore>(); 来注册服务实现
重新运行程序,并使用 Postman 访问,可以重新得到一个新的访问令牌。
控制台输出如下所示:
PS C:\temp\is4\IdentityServer> dotnet run
[02:06:36 Information]
Starting host...
[02:06:37 Information] IdentityServer4.Startup
Starting IdentityServer4 version 3.1.0.0
[02:06:37 Information] IdentityServer4.Startup
You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes
, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch
to a different store implementation.
[02:06:37 Information] IdentityServer4.Startup
Using the default authentication scheme idsrv for IdentityServer
[02:06:37 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for authentication
[02:06:37 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-in
[02:06:37 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-out
[02:06:37 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for challenge
[02:06:37 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for forbid
[02:06:59 Debug] IdentityServer4.Startup
Login Url: /Account/Login
[02:06:59 Debug] IdentityServer4.Startup
Login Return Url Parameter: ReturnUrl
[02:06:59 Debug] IdentityServer4.Startup
Logout Url: /Account/Logout
[02:06:59 Debug] IdentityServer4.Startup
ConsentUrl Url: /consent
[02:06:59 Debug] IdentityServer4.Startup
Consent Return Url Parameter: returnUrl
[02:06:59 Debug] IdentityServer4.Startup
Error Url: /home/error
[02:06:59 Debug] IdentityServer4.Startup
Error Id Parameter: errorId
[02:06:59 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/token matched to endpoint type Token
[02:06:59 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Token, successfully created handler: IdentityServer4.Endpoints.TokenEndpoint
[02:06:59 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
[02:06:59 Debug] IdentityServer4.Endpoints.TokenEndpoint
Start token request.
[02:06:59 Debug] IdentityServer4.Validation.ClientSecretValidator
Start client validation
[02:06:59 Debug] IdentityServer4.Validation.BasicAuthenticationSecretParser
Start parsing Basic Authentication secret
[02:06:59 Debug] IdentityServer4.Validation.PostBodySecretParser
Start parsing for secret in post body
[02:06:59 Debug] IdentityServer4.Validation.SecretParser
Parser found secret: PostBodySecretParser
[02:06:59 Debug] IdentityServer4.Validation.SecretParser
Secret id found: client
[02:06:59 Debug] IdentityServer4.Validation.SecretValidator
Secret validator success: HashedSharedSecretValidator
[02:06:59 Debug] IdentityServer4.Validation.ClientSecretValidator
Client validation success
[02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
Start token request validation
[02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
Start client credentials token request validation
[02:06:59 Debug] IdentityServer4.Validation.TokenRequestValidator
client credentials token request validation success
[02:06:59 Information] IdentityServer4.Validation.TokenRequestValidator
Token request validation success, {"ClientId": "client", "ClientName": null, "GrantType": "client_credentials", "Scopes"
: "api1", "AuthorizationCode": null, "RefreshToken": null, "UserName": null, "AuthenticationContextReferenceClasses": nu
ll, "Tenant": null, "IdP": null, "Raw": {"grant_type": "client_credentials", "client_id": "client", "client_secret": "**
*REDACTED***"}, "$type": "TokenRequestValidationLog"}
[02:06:59 Debug] IdentityServer4.Services.DefaultClaimsService
Getting claims for access token for client: client
[02:06:59 Debug] IdentityServer4.Endpoints.TokenEndpoint
Token request success.
通过 IdentityServer4 提供的扩展方法 AddClientStore() ,还可以使用 IdentityServer4 来完成服务的注册。
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddClientStore<CustomClientStore>();
祝你顺利!
在 IdentityServer4 中创建客户端的更多相关文章
- IdentityServer4 中文文档 -15- (快速入门)添加 JavaScript 客户端
IdentityServer4 中文文档 -15- (快速入门)添加 JavaScript 客户端 原文:http://docs.identityserver.io/en/release/quicks ...
- IdentityServer4 中文文档 -9- (快速入门)使用客户端凭证保护API
IdentityServer4 中文文档 -9- (快速入门)使用客户端凭证保护API 原文:http://docs.identityserver.io/en/release/quickstarts/ ...
- [gRPC] 在 .NET Core 中创建 gRPC 服务端和客户端
gRPC 官网:https://grpc.io/ 1. 创建服务端 1.1 基于 ASP.NET Core Web 应用程序模板创建 gRPC Server 项目. 1.2 编译并运行 2. 创建客户 ...
- 关于 IdentityServer4 中的 Jwt Token 与 Reference Token
OpenID Connect(Core),OAuth 2.0(RFC 6749),JSON Web Token (JWT)(RFC 7519) 之间有着密不可分联系,对比了不同语言的实现,还是觉得 I ...
- IdentityServer4 中文文档 -16- (快速入门)使用 EntityFramework Core 存储配置数据
IdentityServer4 中文文档 -16- (快速入门)使用 EntityFramework Core 存储配置数据 原文:http://docs.identityserver.io/en/r ...
- IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity
IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity 原文:http://docs.identityserver.io/en/release ...
- IdentityServer4 中文文档 -12- (快速入门)添加外部认证支持
IdentityServer4 中文文档 -12- (快速入门)添加外部认证支持 原文:http://docs.identityserver.io/en/release/quickstarts/4_e ...
- IdentityServer4 中文文档 -11- (快速入门)添加基于 OpenID Connect 的用户认证
IdentityServer4 中文文档 -11- (快速入门)添加基于 OpenID Connect 的用户认证 原文:http://docs.identityserver.io/en/releas ...
- IdentityServer4 中文文档 -8- (快速入门)设置和概览
IdentityServer4 中文文档 -8- (快速入门)设置和概览 原文:http://docs.identityserver.io/en/release/quickstarts/0_overv ...
- IdentityServer4 中文文档 -10- (快速入门)使用密码保护API
IdentityServer4 中文文档 -10- (快速入门)使用密码保护API 原文:http://docs.identityserver.io/en/release/quickstarts/2_ ...
随机推荐
- Electron.Net + Linux + Blazor 初尝备忘录
Electron 是使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序的一个框架, Electron.NET 是.net 下对 Electron 的封装实现, 通过它可以比较容 ...
- Java日期时间API系列22-----Jdk8中java.time包中的新的日期时间API类,Month月份和DayOfWeek星期的计算。
Java8中为月份和星期新增的了,Month和DayOfWeek,来处理月份和星期的特殊问题,这2个类都是枚举类,对Month.DayOfWeek源码说明和简单应用,月份英文,月份英文简称,月份中文, ...
- HRM平台 - 组织结构的渲染
组织结构是一个公司的灵魂 ,多使用树型结构 : 页面格式: 渲染头部 : 头部代码: <template> <el-row type="flex" justify ...
- npm install报错 Error: EACCES: permission denied
报错内容 Unable to save binary /root/packageadmin/spring-boot-admin-2.1.6/spring-boot-admin-server-ui/no ...
- 某制造企业基于 KubeSphere 的云原生实践
背景介绍 随着业务升级改造与软件产品专案的增多,常规的物理机和虚拟机方式逐渐暴露出一些问题: 大量服务部署在虚拟机上,资源预估和硬件浪费较大: 大量服务部署在虚拟机上,部署时间和难度较大,自动化程度较 ...
- 项目中maven依赖无法自动下载
[解决方法]: 安装目录conf--修改settting.xml文件在mirrors标签下添加子节点 <mirrors> <!-- mirror | Specifies a repo ...
- Tony Bai · Go语言第一课 _个人笔记 04|初窥门径:一个Go程序的结构是怎样的?
Tony Bai · Go语言第一课 _个人笔记 04|初窥门径:一个Go程序的结构是怎样的? 1.配置国内的Go模块的镜像 配置国内镜像代理(使用阿里云镜像) go env -w GOPROXY=h ...
- DRF-Authention组件源码分析及扩展
drf 认证组件 1.认证组件源码执行流程 在该图中,我把与认证组件无关的代码都删除了,只留下了认证的代码,方便解析.每行注释的开头数字即代表了执行顺序 注意事项: 第5步中的self.authent ...
- 微信小程序、uniapp、vue生命周期钩子函数
生命周期是指从创建到销毁的过程 一.微信小程序 小程序里面有两种生命周期函数,第一个:通过App()来注册一个小程序 ,第二个:通过Page()来注册一个页面 应用生命周期函数 app( ) ap ...
- 今日一学,5道Java基础面试题(附Java面试题及答案整理)
前言 马上国庆了,本来想着给自己放松一下,刷刷博客,慕然回首,自动拆装箱?equals?==?HashCode? instanceof? 似乎有点模糊了,那就大概看一下5道Java基础面试题吧.好记性 ...