从Client应用场景介绍IdentityServer4(二)
原文:从Client应用场景介绍IdentityServer4(二)
本节介绍Client的ClientCredentials客户端模式,先看下画的草图:

一、在Server上添加动态新增Client的API 接口。
为了方便测试,在Server服务端中先添加swagger,添加流程可参考:https://www.cnblogs.com/suxinlcq/p/6757556.html
在ValuesController控制器中注入ConfigurationDbContext上下文,此上下文可用来加载或配置IdentityServer4.EntityFramework的Client、身份信息、API资源信息或CORS数据等。
在ValuesController中实添加以下代码:
private ConfigurationDbContext _context;
public ValuesController(ConfigurationDbContext context)
{
_context = context;
}
添加动态新增Client的API接口:
[HttpPost]
public IActionResult Post([FromBody] IdentityServer4.EntityFramework.Entities.Client client)
{
var res = _context.Clients.Add(client);
if(_context.SaveChanges() >0)
return Ok(true);
else
return Ok(false);
}
控制器代码如下:

二、对Server上的API进行保护
(1)安装IdentityServer4.AccessTokenValidation包
(2)在startup.cs中ConfigureServices方法添加如下代码:
//protect API
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters(); services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false; options.ApiName = "api1";
});
AddAuthentication把Bearer配置成默认模式,将身份认证服务添加到DI中。
AddIdentityServerAuthentication把IdentityServer的access token添加到DI中,供身份认证服务使用。
(3)在startup.cs中Configure方法添加如下代码:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
//} //AddSwagger
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server接口文档");
}); InitializeDatabase(app);
app.UseAuthentication();
app.UseIdentityServer();
app.UseMvc();
}
UseAuthentication将身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。
(4)在ValuesController控制器中添加[Authorize]

(5)在项目属性->调试 中,启动浏览器,并设成swagger,如图:

(6)启动项目,并调用第一个Get接口。

显示Unauthorized(未授权),证明[Authorize]起作用了。
三、搭建Client客户端
(1)新建一个控制台程序,安装IdentityModel包。
(2)添加类IDSHelper.cs,添加客户端请求API接口代码。
public class IDSHelper
{
public static async Task MainAsync()
{
try
{
DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
} TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "Client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync("http://localhost:5000/api/values/");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
catch (Exception ex)
{ }
}
}
(3)修改Program.cs代码,如下:
class Program
{
static void Main(string[] args)
=> IDSHelper.MainAsync().GetAwaiter().GetResult();
}
(4)按Ctrl+F5,可以获取到access token和接口返回值

复制token,用postman调用,成功获取到了接口返回值。

四、测试动态新增Client接口
安装IdentityServer4包。
安装IdentityServer4.EntityFramework包。
在IDSHelper.cs类中添加Post方法:
public static async Task Post()
{
try
{
DiscoveryResponse disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
} TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "Client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken); Client c1 = new Client
{
ClientId = "Test",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
};
string strJson = JsonConvert.SerializeObject(c1 .ToEntity());
HttpContent content = new StringContent(strJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
//由HttpClient发出Post请求
Task<HttpResponseMessage> response = client.PostAsync("http://localhost:5000/api/values/", content); if (response.Result.StatusCode != System.Net.HttpStatusCode.OK)
{
Console.WriteLine(response.Result.StatusCode);
}
else
{
Console.WriteLine(response.Result.Content.ReadAsStringAsync().Result);
}
}
catch (Exception ex)
{ }
}
顺便把main中改成对Post调用:
static void Main(string[] args)
=> IDSHelper.Post().GetAwaiter().GetResult();
按Ctrl+F5,调用新增Client的接口,并成功返回true。

同时可以在数据库中的Client表找到相关记录。需要注意的是,不能添加相同Client ID的Client。
五、在Client中添加Claim信息,并在API接口中对Claim信息进行验证。
关于Claim的介绍可以看这篇文章:http://www.cnblogs.com/stulzq/p/8726002.html
这里把Claim简单当做用户的身份信息使用,修改Post方法里面的Client:
Client c1 = new Client
{
ClientId = "superAdmin",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" },
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Role, "admin")
}
};
可以看出,Claims为List,可以是很多个角色,这里只添加一个。
Ctrl+F5,运行成功添加superAdmin Client。
现在,需要对Server服务端的新增Client接口进行Claim身份验证,添加如下代码:
[Authorize(Roles ="admin")]

然后再客户端修改授权的账号为superadmin。
TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "superAdmin", "secret");
Ctrl+F5运行

问题出现了,返回了Forbidden,没有权限进行访问。
这时候我们上官网查阅了资料,发现在添加Client的Claim时候,IdentityServer EntityFramework会为Claim的role添加一个默认前缀,为client_。所以,实际上它为client_role。
而服务端只能对role进行验证。
此时我们需要把Claim的默认前缀去掉,设置为空ClientClaimsPrefix = "" 。
去掉Server的Role验证,添加形如下面代码的Client。
Client c1 = new Client
{
ClientId = "adminClient",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" },
Claims = new List<Claim>
{
new Claim(JwtClaimTypes.Role, "admin")
},
ClientClaimsPrefix = "" //把client_ 前缀去掉
};
Ctrl+F5,运行成功添加adminClient Client,这次的是Role为admin。
然后重新再Server服务端加上[Authorize(Roles ="admin")]
同时修改验证账号为adminClient。
TokenClient tokenClient = new TokenClient(disco.TokenEndpoint, "adminClient", "secret");
最后运行程序,成功地在[Authorize(Roles ="admin")]权限下访问并新增了Client。

六、需要注意的问题
(1)新增Client到数据库时候,这里需要接收IdentityServer4.EntityFramework.Entities.Client
而不是IdentityServer4.Models.Client,否则API接口在接收和转化Client模型的时候会报错。

(2)此外,本节介绍的Client的AllowedGrantTypes 都为 GrantTypes.ClientCredentials,相应的,客户端请求是,需要用RequestClientCredentialsAsync方法。

最后再次提下,ClientCredentials模式的适用场景:用于和用户无关,服务与服务之间直接交互访问资源。
Server服务端源码地址:https://github.com/Bingjian-Zhu/Server
Client客户端源码地址:https://github.com/Bingjian-Zhu/Client
文中如有错漏,欢迎指正。
从Client应用场景介绍IdentityServer4(二)的更多相关文章
- 从Client应用场景介绍IdentityServer4(五)
原文:从Client应用场景介绍IdentityServer4(五) 本节将在第四节基础上介绍如何实现IdentityServer4从数据库获取User进行验证,并对Claim进行权限设置. 一.新建 ...
- 从Client应用场景介绍IdentityServer4(四)
原文:从Client应用场景介绍IdentityServer4(四) 上节以对话形式,大概说了几种客户端授权模式的原理,这节重点介绍Hybrid模式在MVC下的使用.且为实现IdentityServe ...
- 从Client应用场景介绍IdentityServer4(一)
原文:从Client应用场景介绍IdentityServer4(一) 一.背景 IdentityServer4的介绍将不再叙述,百度下可以找到,且官网的快速入门例子也有翻译的版本.这里主要从Clien ...
- 从Client应用场景介绍IdentityServer4(三)
原文:从Client应用场景介绍IdentityServer4(三) 在学习其他应用场景前,需要了解几个客户端的授权模式.首先了解下本节使用的几个名词 Resource Owner:资源拥有者,文中称 ...
- 消息中间件activemq的使用场景介绍(结合springboot的示例)
一.消息队列概述 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题.实现高性能,高可用,可伸缩和最终一致性架构.是大型分布式系统不可缺少的中间件. 目前在生产环境,使 ...
- Redis 中 5 种数据结构的使用场景介绍
这篇文章主要介绍了Redis中5种数据结构的使用场景介绍,本文对Redis中的5种数据类型String.Hash.List.Set.Sorted Set做了讲解,需要的朋友可以参考下 一.redis ...
- 高效而稳定的企业级.NET Office 组件Spire(.NET组件介绍之二)
在项目开发中,尤其是企业的业务系统中,对文档的操作是非常多的,有时几乎给人一种错觉的是”这个系统似乎就是专门操作文档的“.毕竟现在的很多办公中大都是在PC端操作文档等软件,在这些庞大而繁重的业务中,单 ...
- cWeb开发框架,基于asp.net的cWeb应用开发平台介绍(二)
cWeb是基于微软的.Net Framework 4框架,数据库是sql server 2008 r2. cWeb开发框架下载,点击这里去下载. cWeb开发框架借鉴三层架构理论分为三层,分别是:cD ...
- GCD介绍(二): 多核心的性能
GCD介绍(二): 多核心的性能 概念 为了在单一进程中充分发挥多核的优势,我们有必要使用多线程技术(我们没必要去提多进程,这玩意儿和GCD没关系).在低层,GCD全局dispatc ...
随机推荐
- Android, IOS 史上最强多语言国际化,不仅第一次会尾随系统,并且会保存用户的语言设置
劲爆消息,我提供源代码了.你能够先看完再下载.也能够先下载再看完, android源代码地址: https://github.com/hebiao6446/------Bluetooth-Androi ...
- TF卡电压 SD卡引脚
//////////////////////////////////////////////////////////////////////////////////////////////////// ...
- ZOJ 2770 Burn the Linked Camp 差分约束 ZOJ排名第一~
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1770 题目大意: 陆逊为了火烧连营七百里,派出了间谍刺探敌情,得之刘备的军营以 ...
- UVA 11889 - Benefit 可直接枚举
看题传送门 题目大意: 输入两个整数A和C,求最小的整数B,使得lcm(A,B)=C.如果无解,输出NO SOLUTION 思路: A*B=C*gcd(A,B) 所以 B / gcd(A,B) = C ...
- ArcEngine数据删除几种方法和性能比较
转自原文 ArcEngine数据删除几种方法和性能比较 一. 几种删除方法代码 1. 查询结果中删除 private void Delete1(IFeatureClass PFeatureclas ...
- [Nuxt] Load Data from APIs with Nuxt and Vuex
In a server-rendered application, if you attempt to load data before the page renders and the data f ...
- Unity插件之NGUI学习(5)—— 创建Label图文混排及文字点击
创建一个新的Scene,并按 Unity插件之NGUI学习(2)创建UI Root. 准备工作,制作Font.如今Project窗体创建一个Font目录.然后从系统自带字体目录中选择自己须要的字体,我 ...
- 组合搜索(combinatorial search)在算法求解中的应用
1. 分治.动态规划的局限性 没有合适的分割方式时,就不能使用分治法: 没有合适的子问题或占用内存空间太大时,就不能用动态规划: 此时还需要回到最基本的穷举搜索算法. 穷举搜索(exhaustive ...
- DateTime与timeStamp的转换
DateTime转换为timeStamp: DateTime dt = DateTime.Now; DateTime startTime = TimeZone.CurrentTi ...
- Swift3.0 功能一(持续更新)
修改项目名称两种方式 1.Bundle name 2.Bundle display name try 三种处理异常的方式 // 在swift中提供三种处理异常的方式 // 方式一:try方式 程序员手 ...