从上一篇关于 快速搭建简易项目中,经过手动或者官方模板的方式简易的实现了咱们的IdentityServer受权服务器搭建,并作了相应的配置和UI配置,实现了获取Token方式。javascript
而其中咱们也注意到了三点就是,有哪些用户(users)能够经过哪些客户端(clents)来访问咱们的哪些API保护资源 (API)。html
因此在这一篇中,咱们将经过多种受权模式中的客户端凭证模式进行说明,主要针对介绍IdentityServer保护API的资源,客户端认证受权访问API资源。前端
Client Credentials
客户端凭证模式:客户端(Client)请求受权服务器验证,经过验证就发access token,Client直接以已本身的名义去访问Resource server的一些受保护资源。java
用户使用这个令牌访问资源服务器,当令牌失效时使用刷新令牌去换取新的令牌(刷新令牌有效时间大于访问令牌,刷新令牌的功能不作详细介绍)git
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。github
这种模式通常只用在服务端与服务端之间的认证数据库
适用于没有前端的命令行应用,即在命令行请求令牌
认证服务器不提供像用户数据这样的重要资源,仅仅是有限的只读资源或者一些开放的API。例如使用了第三方的静态文件服务,如Google Storage或Amazon S3。这样,你的应用须要经过外部API调用并以应用自己而不是单个用户的身份来读取或修改这些资源。这样的场景就很适合使用客户端证书受权。json
+---------+ +---------------+ | | | | | |>--(A)- Client Authentication --->| Authorization | | Client | | Server | | |<--(B)---- Access Token ---------<| | | | | | +---------+ +---------------+
客户端凭据许可流程描述c#
(A)客户端与受权服务器进行身份验证并向令牌端点请求访问令牌。
(B)受权服务器对客户端进行身份验证,若是有效,颁发访问令牌。api
参数 | 是否必须 | 含义 |
---|---|---|
grant_type | 必需 | 受权类型,值固定为“client_credentials”。 |
scope | 可选 | 表示受权范围。 |
示例:
客户端身份验证两种方式
一、Authorization: Bearer base64(resourcesServer:123)
二、client_id(客户端标识),client_secret(客户端秘钥)。
POST /token HTTP/1.1 Host: authorization-server.com grant_type=client_credentials &client_id=xxxxxxxxxx &client_secret=xxxxxxxxxx
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。
搭建认证受权服务
IdentityServer4
程序包
创建配置内容文件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" } }, }; }
在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(); }
在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项目服务的方式,具体说明能够看上一篇的内容。
实现对API资源进行保护
IdentityServer4.AccessTokenValidation 包
在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中,供身份认证服务使用。
在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] , 在进行请求资源的时候,需进行认证受权经过后,才能进行访问。
实现对API资源的访问和获取资源
IdentityModel 包
客户端与受权服务器进行身份验证并向令牌端点请求访问令牌。受权服务器对客户端进行身份验证,若是有效,颁发访问令牌。
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; }
要将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; } }
以上展现的代码有不明白的,能够看本篇项目源码,项目地址为 :
注意,若是你的代码没问题,可是依然报错,好比“无效的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。