1. 程式人生 > 實用技巧 >04 建立 IdentityServer4 專案,Client Credentials

04 建立 IdentityServer4 專案,Client Credentials

原文:https://www.yuque.com/yuejiangliu/dotnet/cvmvr9


04 建立 IdentityServer4 專案,Client Credentials.mp4 (159.9 MB)

大綱:

  • 介紹 ldentityServer4
  • 搭建 IdentityServer4 專案
  • 保護客戶端:使用 OAuth 2.0 Client Credentials

一、介紹 IdentityServer4

參考官方文件 Terminology

(官方 Terminology 配圖)

IdentityServer4 是一個 OpenID Connect provider,它的主要功能包括:

  • protect your resources
  • authenticate users using a local account store or via an external identity provider
  • provide session management and single sign-on(單點登入)
  • manage and authenticate clients
  • issue identity and access tokens to clients
  • validate tokens

Packaging and Builds 裡面列出了 IdentityServer 4 相關的包的位置。

二、搭建 IdentityServer4 專案

通過 dotnet new is4inmem --name Idp 命令建立一個 IdentityServer4 with In-Memory Stores and Test Users 專案。

檢視專案 Startup ConfigureServices 的程式碼,依次匹配官方示意圖中的每個部分。

var builder = services.AddIdentityServer(options =>
{
    options.Events.RaiseErrorEvents = true;
    options.Events.RaiseInformationEvents = true;
    options.Events.RaiseFailureEvents = true;
    options.Events.RaiseSuccessEvents = true;
})
    .AddTestUsers(TestUsers.Users);

// in-memory, code config
builder.AddInMemoryIdentityResources(Config.GetIdentityResources());
builder.AddInMemoryApiResources(Config.GetApis());

builder.AddInMemoryClients(Config.GetClients());

三、使用 OAuth 2.0 Client Credentials

參考官方文件“建立 Console 客戶端,通過 Client Credentials 向 IdentityServer 請求 Access Token,並訪問受保護資源”。

Client Credentials: The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.

1、C# 7.1 非同步的 Main 方法

建立 console client 時通過在 csproj 裡面指定 Language Version 使專案支援非同步的 Main 方法:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
  ...
</Project>

老寫法:

static void Main(string[] args)
{
    var client = new HttpClient();
    Task.Run(async () =>
    {
        var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
    }).GetAwaiter().GetResult();
}

7.1:

static async Task Main(string[] args)
{
    var client = new HttpClient();

    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
}

2、嘗試訪問身份認證資源

在 Idp 中給 console client 新增 OpenID Scope:

public static IEnumerable<Client> GetClients()
{
    return new[]
    {
        // client credentials flow client
        new Client
        {
            ClientId = "console client",
            ClientName = "Client Credentials Client",

            AllowedGrantTypes = GrantTypes.ClientCredentials,
            ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },

            AllowedScopes = { "api1" ,IdentityServerConstants.StandardScopes.OpenId}
        }
    };
}

在 console client 中訪問 OpenID 資源:

static async Task Main(string[] args)
{
    // Discoverty endpoint
    var client = new HttpClient();

    var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");

    if (disco.IsError)
    {
        Console.WriteLine(disco.Error);
        return;
    }

    // Request access token
    var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
    {
        Address = disco.TokenEndpoint,
        ClientId = "console client",
        ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A",
        Scope = "api1 openid"
    });

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        // invalid_scope
        return;
    }
    ...
}

tokenResponse 報錯 invalid_scope。這是因為 Client Credentials 授權方式不代表任何使用者,所以無法通過它訪問 IdentityServer 上身份認證的資源。

3、建立並訪問 Api1Resource

由於無法通過 Client Credentials 訪問 OpenID,也就不方便演示。所以又參考官方教程建立了 Api1Resource。

Api1Resource 中返回了 User.Claims 的一些資訊:

[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

修改 Startup,新增 Authentication 服務,並將 Authentication 中介軟體新增到管道中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore()
        .AddAuthorization()
        .AddJsonFormatters();

    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "http://localhost:5000";
            options.RequireHttpsMetadata = false;

            options.Audience = "api1";
        });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();

    app.UseMvc();
}

AddAuthentication adds the authentication services to DI and configures "Bearer" as the default scheme. UseAuthentication adds the authentication middleware to the pipeline so authentication will be performed automatically on every call into the host.


Navigating to the controller http://localhost:5001/identity on a browser should return a 401 status code. This means your API requires a credential and is now protected by IdentityServer.


修改 console client 中的程式碼,以訪問 Api1Resource,並列印 Claim 資訊:

static async Task Main(string[] args)
{
    // Discoverty endpoint
    ...
    // Request access token
    ...
    // Call Api1Resource
    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(JArray.Parse(content));
    }

    Console.ReadKey();
}

效果: