IdentityServer4系列 | 客户端凭证模式
一、前言
从上一篇关于 快速搭建简易项目中,通过手动或者官方模板的方式简易的实现了我们的IdentityServer授权服务器搭建,并做了相应的配置和UI配置,实现了获取Token方式。
而其中我们也注意到了三点就是,有哪些用户(users)可以通过哪些客户端(clents)来访问我们的哪些API保护资源 (API)。
所以在这一篇中,我们将通过多种授权模式中的客户端凭证模式进行说明,主要针对介绍IdentityServer保护API的资源,客户端认证授权访问API资源。

二、初识
Client Credentials 客户端凭证模式:客户端(Client)请求授权服务器验证,通过验证就发access token,Client直接以已自己的名义去访问Resource server的一些受保护资源。
用户使用这个令牌访问资源服务器,当令牌失效时使用刷新令牌去换取新的令牌(刷新令牌有效时间大于访问令牌,刷新令牌的功能不做详细介绍)
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
2.1 适用范围
这种模式一般只用在服务端与服务端之间的认证
适用于没有前端的命令行应用,即在命令行请求令牌
认证服务器不提供像用户数据这样的重要资源,仅仅是有限的只读资源或者一些开放的API。例如使用了第三方的静态文件服务,如Google Storage或Amazon S3。这样,你的应用需要通过外部API调用并以应用本身而不是单个用户的身份来读取或修改这些资源。这样的场景就很适合使用客户端证书授权。
2.2 Client Credentials流程:
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
客户端凭据许可流程描述
(A)客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。
(B)授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。
2.2.1 过程详解
访问令牌请求
| 参数 | 是否必须 | 含义 |
|---|---|---|
| grant_type | 必需 | 授权类型,值固定为“client_credentials”。 |
| scope | 可选 | 表示授权范围。 |
示例:
客户端身份验证两种方式
1、Authorization: Bearer base64(resourcesServer:123)
2、client_id(客户端标识),client_secret(客户端秘钥)。
POST /token HTTP/1.1
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
2.2.2 访问令牌响应
刷新令牌不应该包含在内。
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"Bearer",
"expires_in":3600,
"scope":"server"
}
三、实践
在示例实践中,我们将创建一个授权访问服务,定义一个API和要访问它的客户端,客户端通过IdentityServer上请求访问令牌,并使用它来访问API。
3.1 搭建 Authorization Server 服务
搭建认证授权服务
3.1.1 安装Nuget包
IdentityServer4程序包
3.1.2 配置内容
建立配置内容文件Config.cs
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("client_scope1")
};
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("api1","api1")
{
Scopes={"client_scope1" }
}
};
public static IEnumerable<Client> Clients =>
new Client[]
{
// m2m client credentials flow client
new Client
{
ClientId = "credentials_client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },
AllowedScopes = { "client_scope1" }
},
};
}
3.1.3 注册服务
在startup.cs中ConfigureServices方法添加如下代码:
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddIdentityServer();
// .AddTestUsers(TestUsers.Users);
// in-memory, code config
builder.AddInMemoryIdentityResources(Config.IdentityResources);
builder.AddInMemoryApiScopes(Config.ApiScopes);
builder.AddInMemoryApiResources(Config.ApiResources);
builder.AddInMemoryClients(Config.Clients);
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
}
3.1.4 配置管道
在startup.cs中Configure方法添加如下代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseIdentityServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
以上内容是快速搭建简易IdentityServer项目服务的方式,具体说明可以看上一篇的内容。
3.2 搭建API资源
实现对API资源进行保护
3.2.1 快速搭建一个API项目
3.2.2 安装Nuget包
IdentityServer4.AccessTokenValidation 包
3.2.3 注册服务
在startup.cs中ConfigureServices方法添加如下代码:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthorization();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5001";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
}
AddAuthentication把Bearer配置成默认模式,将身份认证服务添加到DI中。
AddIdentityServerAuthentication把IdentityServer的access token添加到DI中,供身份认证服务使用。
3.2.4 配置管道
在startup.cs中Configure方法添加如下代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
UseAuthentication将身份验证中间件添加到管道中;
UseAuthorization 将启动授权中间件添加到管道中,以便在每次调用主机时执行身份验证授权功能。
2.5 添加API资源接口
[Route("api/[Controller]")]
[ApiController]
public class IdentityController:ControllerBase
{
[HttpGet("getUserClaims")]
[Authorize]
public IActionResult GetUserClaims()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
在IdentityController 控制器中添加 [Authorize] , 在进行请求资源的时候,需进行认证授权通过后,才能进行访问。
3.3 搭建Client客户端
实现对API资源的访问和获取资源
3.3.1 搭建一个窗体程序
3.3.2 安装Nuget包
IdentityModel 包
3.3.3 获取令牌
客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。
IdentityModel 包括用于发现 IdentityServer 各个终结点(EndPoint)的客户端库。
我们可以使用从 IdentityServer 元数据获取到的Token终结点请求令牌:
private void getToken_Click(object sender, EventArgs e)
{
var client = new HttpClient();
var disco = client.GetDiscoveryDocumentAsync(this.txtIdentityServer.Text).Result;
if (disco.IsError)
{
this.tokenList.Text = disco.Error;
return;
}
//请求token
tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId =this.txtClientId.Text,
ClientSecret = this.txtClientSecret.Text,
Scope = this.txtApiScopes.Text
}).Result;
if (tokenResponse.IsError)
{
this.tokenList.Text = disco.Error;
return;
}
this.tokenList.Text = JsonConvert.SerializeObject(tokenResponse.Json);
this.txtToken.Text = tokenResponse.AccessToken;
}
3.3.4 调用API
要将Token发送到API,通常使用HTTP Authorization标头。 这是使用
SetBearerToken扩展方法完成。
private void getApi_Click(object sender, EventArgs e)
{
//调用认证api
if (string.IsNullOrEmpty(txtToken.Text))
{
MessageBox.Show("token值不能为空");
return;
}
var apiClient = new HttpClient();
//apiClient.SetBearerToken(tokenResponse.AccessToken);
apiClient.SetBearerToken(this.txtToken.Text);
var response = apiClient.GetAsync(this.txtApi.Text).Result;
if (!response.IsSuccessStatusCode)
{
this.resourceList.Text = response.StatusCode.ToString();
}
else
{
this.resourceList.Text = response.Content.ReadAsStringAsync().Result;
}
}
以上展示的代码有不明白的,可以看本篇项目源码,项目地址为 :
3.4 效果
3.4.1 项目测试

3.4.2 postman测试

四、问题

注意,如果你的代码没问题,但是依然报错,比如“无效的scope”,“Audience validation failed”等问题。
在3.1.x 到 4.x 的变更中,ApiResource 的 Scope 正式独立出来为 ApiScope 对象,区别ApiResource 和 Scope的关系, Scope 是属于ApiResource 的一个属性,可以包含多个Scope。
所以在配置ApiResource、ApiScope、Clients中,我们有些地方需要注意:
在3.x版本中
public static IEnumerable<ApiResource> GetApiResources()
{
return new[] { new ApiResource("api1", "api1") };
}
改成4.x版本为
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("api1","api1")
{
Scopes={"client_scope1" }
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("client_scope1")
};
因此,
这里比之前3.x版本多了一个添加ApiScopes的方法:
builder.AddInMemoryApiScopes(Config.ApiScopes);
因为接下来有要保护的API资源,所以需要添加一行:
builder.AddInMemoryApiResources(Config.ApiResources);
- 如果在4.x版本中,不添加ApiScopes方法的话,在获取token令牌的时候一直“无效的scope”等错误
- 在授权访问保护资源的时候,如果
ApiResource中不添加Scopes, 会一直报Audience validation failed错误,得到401错误,所以在4.x版本中写法要不同于3.x版本
所以,需要注意的是4.x版本的ApiScope和ApiResource是分开配置的,然后在ApiResource中一定要添加Scopes。
五、总结
- 本篇主要以客户端凭证模式进行授权,我们通过创建一个认证授权访问服务,定义一个API和要访问它的客户端,客户端通过IdentityServer上请求访问令牌,并使用它来控制访问API。
- 在文中可能出现的问题,我们通过查找解决,以及前后版本之间的差异,并总结说明问题。
- 在后续会对其中的其他授权模式,数据库持久化问题,以及如何应用在API资源服务器中和配置在客户端中,会进一步说明。
- 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
- 项目地址
六、附加
IdentityServer4系列 | 客户端凭证模式的更多相关文章
- IdentityServer4 (1) 客户端授权模式(Client Credentials)
写在前面 1.源码(.Net Core 2.2) git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git 2.相关章节 2.1. ...
- IdentityServer4系列 | 授权码模式
一.前言 在上一篇关于简化模式中,通过客户端以浏览器的形式请求IdentityServer服务获取访问令牌,从而请求获取受保护的资源,但由于token携带在url中,安全性方面不能保证.因此,我们可以 ...
- ASP.NET Core3.1使用IdentityServer4中间件系列随笔(三):创建使用[ClientCredentials客户端凭证]授权模式的客户端
配套源码:https://gitee.com/jardeng/IdentitySolution 上一篇<ASP.NET Core3.1使用IdentityServer4中间件系列随笔(二):创建 ...
- IdentityServer4系列 | 资源密码凭证模式
一.前言 从上一篇关于客户端凭证模式中,我们通过创建一个认证授权访问服务,定义一个API和要访问它的客户端,客户端通过IdentityServer上请求访问令牌,并使用它来控制访问API.其中,我们也 ...
- IdentityServer4 (3) 授权码模式(Authorization Code)
写在前面 1.源码(.Net Core 2.2) git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git 2.相关章节 2.1. ...
- IdentityServer4系列 | 简化模式
一.前言 从上一篇关于资源密码凭证模式中,通过使用client_id和client_secret以及用户名密码通过应用Client(客户端)直接获取,从而请求获取受保护的资源,但是这种方式存在clie ...
- IdentityServer4(一)使用客户端凭证方式
这个篇文章主要是记录自己参考官方文档搭建身份认证的过程 使用的.NET Core2.2 参考地址:https://identityserver4.readthedocs.io/en/latest/qu ...
- IdentityServer4 中文文档 -9- (快速入门)使用客户端凭证保护API
IdentityServer4 中文文档 -9- (快速入门)使用客户端凭证保护API 原文:http://docs.identityserver.io/en/release/quickstarts/ ...
- Core篇——初探IdentityServer4(客户端模式,密码模式)
Core篇——初探IdentityServer4(客户端模式,密码模式) 目录 1.Oatuth2协议的客户端模式介绍2.IdentityServer4客户端模式实现3.Oatuth2协议的密码模式介 ...
随机推荐
- numpy矩阵
一.创建矩阵 Numpy提供了ndarray来进行矩阵的操作,在Numpy中 矩阵继承于NumPy中的二维数组对象,但矩阵区别于数组,不可共用数组的运算规律. 1.mat("第0行:第1行: ...
- Polyglot Translators: Let's do i18n easier! 一款国际化插件小助手!
在做国际化文本有关的工作时, 是否厌倦了在不同应用或者网页之间频繁地切换进行中文, 繁体, 英文甚至韩文日文的文本翻译工作? 好吧, 我就是受不了频繁在进行文本字符串的转换, 还得跑到百度翻译上面搜索 ...
- linux的mysql修改默认端口3306
linux 修改mysql默认端口3306 cd /etc/mysql/my.cnf 修改两处 客户端的port=3306 和mysqld的服务器端口port=3306 [client] port=3 ...
- 7_Reverse Integer
7.Reverse Integer Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 ...
- 如约而至,.NET 5.0 正式发布
作者:Richard 翻译:精致码农-王亮 原文:http://dwz.win/WFz 说明:文章太长,后面关于 C# 9 语言新特性及平台新特性的部分没有翻译,这部分基本都是代码示例,请直接阅读原文 ...
- tp3.2 php sdk上传七牛云
//获取上传token Vendor('sdk.autoload'); $accessKey='********'; $secretKey='*******'; $auth=new \Qiniu\Au ...
- Ubuntu18.04上安装CUDA_10.1(nvidia-driver)和cuDNN_7.6.5
本文是在Ubuntu18.04.5服务器上安装CUDA_10.1(nvidia-driver455)和cuDNN_7.6.5, Ubuntu 18.04.5 CUDA_10.1 (nvidia-dri ...
- 重要 | Spark分区并行度决定机制
最近经常有小伙伴在本公众号留言,核心问题都比较类似,就是虽然接触Spark有一段时间了,但是搞不明白一个问题,为什么我从HDFS上加载不同的文件时,打印的分区数不一样,并且好像spark.defaul ...
- rgw实现nfs的首测
功能介绍 关于rgw实现nfs接口这个,刚接触的人可能并不清楚这个是个什么样的服务架构,rgw是ceph里面的对象存储接口,而nfs则是纯正的网络文件系统接口,这二者如何结合在一起,关于这个,有几个相 ...
- CSS属性(display)
1.display属性 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset= ...