IdentityServer4系列 | 客戶端憑證模式

1、前言

從上一篇關於 快速搭建簡易項目中,經過手動或者官方模板的方式簡易的實現了咱們的IdentityServer受權服務器搭建,並作了相應的配置和UI配置,實現了獲取Token方式。javascript

而其中咱們也注意到了三點就是,有哪些用戶(users)能夠經過哪些客戶端(clents)來訪問咱們的哪些API保護資源 (API)。html

因此在這一篇中,咱們將經過多種受權模式中的客戶端憑證模式進行說明,主要針對介紹IdentityServer保護API的資源,客戶端認證受權訪問API資源。前端

2、初識

Client Credentials 客戶端憑證模式:客戶端(Client)請求受權服務器驗證,經過驗證就發access token,Client直接以已本身的名義去訪問Resource server的一些受保護資源。java

用戶使用這個令牌訪問資源服務器,當令牌失效時使用刷新令牌去換取新的令牌(刷新令牌有效時間大於訪問令牌,刷新令牌的功能不作詳細介紹)git

這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。github

2.1 適用範圍

這種模式通常只用在服務端與服務端之間的認證數據庫

適用於沒有前端的命令行應用,即在命令行請求令牌

認證服務器不提供像用戶數據這樣的重要資源,僅僅是有限的只讀資源或者一些開放的API。例如使用了第三方的靜態文件服務,如Google Storage或Amazon S3。這樣,你的應用須要經過外部API調用並以應用自己而不是單個用戶的身份來讀取或修改這些資源。這樣的場景就很適合使用客戶端證書受權。json

2.2 Client Credentials流程:

+---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

客戶端憑據許可流程描述c#

(A)客戶端與受權服務器進行身份驗證並向令牌端點請求訪問令牌。
(B)受權服務器對客戶端進行身份驗證,若是有效,頒發訪問令牌。api

2.2.1 過程詳解


訪問令牌請求
參數 是否必須 含義
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

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"
}

3、實踐

在示例實踐中,咱們將建立一個受權訪問服務,定義一個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測試

4、問題

注意,若是你的代碼沒問題,可是依然報錯,好比「無效的scope」,「Audience validation failed」等問題。

在3.1.x 到 4.x 的變動中,ApiResourceScope 正式獨立出來爲 ApiScope 對象,區別ApiResourceScope的關係, 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);
  1. 若是在4.x版本中,不添加ApiScopes方法的話,在獲取token令牌的時候一直「無效的scope」等錯誤
  2. 在受權訪問保護資源的時候,若是ApiResource中不添加Scopes, 會一直報Audience validation failed錯誤,獲得401錯誤,因此在4.x版本中寫法要不一樣於3.x版本

因此,須要注意的是4.x版本的ApiScope和ApiResource是分開配置的,而後在ApiResource中必定要添加Scopes。

5、總結

  1. 本篇主要以客戶端憑證模式進行受權,咱們經過建立一個認證受權訪問服務,定義一個API和要訪問它的客戶端,客戶端經過IdentityServer上請求訪問令牌,並使用它來控制訪問API。
  2. 在文中可能出現的問題,咱們經過查找解決,以及先後版本之間的差別,並總結說明問題。
  3. 在後續會對其中的其餘受權模式,數據庫持久化問題,以及如何應用在API資源服務器中和配置在客戶端中,會進一步說明。
  4. 若是有不對的或不理解的地方,但願你們能夠多多指正,提出問題,一塊兒討論,不斷學習,共同進步。
  5. 項目地址

6、附加

Client Authentication認證

client-credentials資料

相關文章
相關標籤/搜索