IdentityServer4(二):使用Client Credentials模式控制API的訪問

本文基於官方文章翻譯和修改

客戶端憑證模式(Client Credentials)模式

客戶端憑證模式模式是IdentityServer4中控制API訪問的基本模式,我們通常通過定義一個API和訪問客戶端,客戶端通過客戶端ID請求Identity Server以獲取訪問的token

本篇將通過創建一個用戶認證授權中心來保護API

編寫Identity server

Install-Package IdentityServer4 -Version 3.1.0

定義API Resource和客戶端

public static class Config
{
    // Defining an API Resource
    public static IEnumerable<ApiResource> Apis =>
        new List<ApiResource>{
            new ApiResource("CodeSnippets.WebApi","CodeSnippets API")
        };

    // Defining Client
    public static IEnumerable<Client> Clients =>
        new List<Client> {
            new Client
            {
                ClientId="client",

                // no interactive user, use the clientid/secret for authentication
                AllowedGrantTypes=GrantTypes.ClientCredentials,

                // secret for authentication
                ClientSecrets={
                    new Secret("secret".Sha256())
                },

                // scopes that client has access to
                AllowedScopes={ "CodeSnippets.WebApi" }
            }
        };
}

配置IdentityServer

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
        .AddInMemoryApiResources(Config.Apis)
        .AddInMemoryClients(Config.Clients);
    // builder.AddDeveloperSigningCredential();
}

配置完成後運行應用,訪問:http://localhost:5000/.well-known/openid-configuration將看到所謂的發現文檔(發現文檔是身份服務器中的標準端點,我們的客戶端和API將使用發現文檔來下載必要的配置數據)。
在這裏插入圖片描述
首次啓動時,IdentityServer將爲我們創建一個開發人員簽名密鑰,該文件名爲tempkey.rsa。我們無需將該文件簽入源代碼管理中,如果不存在該文件將被重新創建。

創建API

  • 新建一個WebApi:
[Route("identity")]
[ApiController]
[Authorize]
public class IdentityController : ControllerBase
{
    public IActionResult Get()
    {
        return new JsonResult(User.Claims.Select(c => new { c.Type, char.MinValue }));
    }
}

配置launchSettings.json使其在端口5001運行

  • 配置API
public void ConfigureServices(IServiceCollection services)
{
    // 將身份驗證服務添加到DI並配置Bearer爲默認方案。
    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", option =>
        {
            option.Authority = "http://localhost:5000";
            option.RequireHttpsMetadata = false;
            option.Audience = "CodeSnippets.WebApi";
        });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();

    app.UseAuthentication();    // 將身份驗證中間件添加到管道中,以便對主機的每次調用都將自動執行身份驗證。
    app.UseAuthorization();    // 添加了授權中間件,以確保匿名客戶端無法訪問我們的API端點。

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute().RequireAuthorization();
        //endpoints.MapHealthChecks("")
    });
}

此時運行API,訪問http://localhost:5001/identity返回401 Unauthorized

創建客戶端

客戶端請求訪問令牌,然後使用該令牌訪問API

IdentityServer上的令牌端點實現了OAuth 2.0協議,我們可以使用原始HTTP來訪問它。但是,我們有一個名爲IdentityModel的客戶端庫,該客戶端庫將協議交互封裝在易於使用的API中:

Install-Package IdentityModel -Version 4.1.1

  • 步驟一 獲取被發現文檔
    IdentityModel包括一個與發現端點一起使用的客戶端庫。這樣,我們只需要知道IdentityServer的基地址-實際的端點地址就可以從元數據中讀取。

  • 步驟二
    使用發現文檔中的信息向IdentityServer請求令牌以訪問API

  • 步驟三
    調用API

static async Task Main(string[] args)
{
    // 1.discover endpoints from metadata
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }

    // 2.request token
    var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = "client",
        ClientSecret = "secret",
        Scope = "CodeSnippets.WebApi"
    });

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }
    Console.WriteLine(tokenResponse.Json);
    Console.WriteLine("*".PadLeft(50, '*'));
    // 3.call api
    var apiClient = new HttpClient();
    apiClient.SetBearerToken(tokenResponse.AccessToken);

    var response = await apiClient.GetAsync("http://localhost:5001/identity");
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
    }
    else
    {
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(content);
        //Console.WriteLine(Newtonsoft.Json.Linq.JArray.Parse(content));
    }
}

以上代碼執行結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章