ASP.NET Core3.1使用IdentityServer4中間件系列隨筆(四):創建使用[ResourceOwnerPassword-資源所有者密碼憑證]授權模式的客戶端

本篇將創建使用[ResourceOwnerPassword-資源所有者密碼憑證]授權模式的客戶端,來對受保護的API資源進行訪問。

接上一篇項目,在IdentityServer項目Config.cs中添加一個客戶端

 

/// 資源所有者密碼憑證(ResourceOwnerPassword)
///     Resource Owner其實就是User,所以可以直譯爲用戶名密碼模式。
///     密碼模式相較於客戶端憑證模式,多了一個參與者,就是User。
///     通過User的用戶名和密碼向Identity Server申請訪問令牌。
new Client
{ 
    ClientId = "client1",
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowedScopes = { "api1" }
}
View Code

再添加一個用戶的集合(測試數據來自IdentityServer官方)。

完整的Config.cs代碼

using System.Collections.Generic;
using System.Security.Claims;

using IdentityModel;

using IdentityServer4.Models;
using IdentityServer4.Test;

namespace IdentityServer
{
    /// <summary>
    /// IdentityServer資源和客戶端配置文件
    /// </summary>
    public static class Config
    {
        /// <summary>
        /// API資源集合
        ///     如果您將在生產環境中使用此功能,那麼給您的API取一個邏輯名稱就很重要。
        ///     開發人員將使用它通過身份服務器連接到您的api。
        ///     它應該以簡單的方式向開發人員和用戶描述您的api。
        /// </summary>
        public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("api1", "My API") };

        /// <summary>
        /// 客戶端集合
        /// </summary>
        public static IEnumerable<Client> Clients =>
            new List<Client>
            {
                /// 客戶端模式(Client Credentials)
                ///     可以將ClientId和ClientSecret視爲應用程序本身的登錄名和密碼。
                ///     它將您的應用程序標識到身份服務器,以便它知道哪個應用程序正在嘗試與其連接。
                new Client
                { 
                    //客戶端標識
                    ClientId = "client",
                    //沒有交互用戶,使用clientid/secret進行身份驗證,適用於和用戶無關,機器與機器之間直接交互訪問資源的場景。
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    //認證密鑰
                    ClientSecrets = { new Secret("secret".Sha256()) },
                    //客戶端有權訪問的作用域
                    AllowedScopes = { "api1" }
                },
                /// 資源所有者密碼憑證(ResourceOwnerPassword)
                ///     Resource Owner其實就是User,所以可以直譯爲用戶名密碼模式。
                ///     密碼模式相較於客戶端憑證模式,多了一個參與者,就是User。
                ///     通過User的用戶名和密碼向Identity Server申請訪問令牌。
                new Client
                {
                    ClientId = "client1",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    ClientSecrets = { new Secret("secret".Sha256()) },
                    AllowedScopes = { "api1" }
                }
            };

        /// <summary>
        /// 用戶集合
        /// </summary>
        public static List<TestUser> Users =>
            new List<TestUser>
            {
                new TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
                    Claims =
                    {
                        new Claim(JwtClaimTypes.Name, "Alice Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Alice"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.Email, "[email protected]"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                        new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
                    }
                },
                new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
                    Claims =
                    {
                        new Claim(JwtClaimTypes.Name, "Bob Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Bob"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.Email, "[email protected]"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
                        new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
                        new Claim("location", "somewhere")
                    }
                }
            };
    }
}
View Code

我們使用Postman來獲取ResourceOwnerPassword這種模式的AcceccToken

與上一種 Client Credentials 模式不同的是 client_id 使用 client1,grant_type 由原來的 client_credentials 改爲 password,多了 usernamepassword 兩個參數,使用用戶名密碼 alice / alice 來登錄

 

 

2、創建一個名爲 ResourceOwnerPasswordConsoleApp 的控制檯客戶端應用。

 創建完成後的項目截圖

 

3、添加nuget包:IdentityModel

在Program.cs編寫代碼

using System;
using System.Net.Http;
using System.Threading.Tasks;

using IdentityModel.Client;

using Newtonsoft.Json.Linq;

namespace ResourceOwnerPasswordConsoleApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            bool verifySuccess = false;
            TokenResponse tokenResponse = null;
            while (!verifySuccess)
            {
                Console.WriteLine("請輸入用戶名:");
                string userName = Console.ReadLine();
                Console.WriteLine("請輸入密碼:");
                string password = Console.ReadLine();

                //discovery endpoint - 發現終結點
                HttpClient client = new HttpClient();
                DiscoveryDocumentResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
                if (disco.IsError)
                {
                    Console.WriteLine($"[DiscoveryDocumentResponse Error]: {disco.Error}");
                    return;
                }

                //request assess token - 請求訪問令牌
                tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "client1",
                    ClientSecret = "secret",
                    Scope = "api1",
                    UserName = userName,
                    Password = password
                });
                if (tokenResponse.IsError)
                {
                    //ClientId 與 ClientSecret 錯誤,報錯:invalid_client
                    //Scope 錯誤,報錯:invalid_scope
                    //UserName 與 Password 錯誤,報錯:invalid_grant
                    string errorDesc = tokenResponse.ErrorDescription;
                    if (string.IsNullOrEmpty(errorDesc)) errorDesc = "";
                    if (errorDesc.Equals("invalid_username_or_password"))
                    {
                        Console.WriteLine("用戶名或密碼錯誤,請重新輸入!");
                    }
                    else
                    {
                        Console.WriteLine($"[TokenResponse Error]: {tokenResponse.Error}, [TokenResponse Error Description]: {errorDesc}");
                    }
                    Console.WriteLine("");
                    continue;
                }
                else
                {
                    Console.WriteLine("");
                    Console.WriteLine($"Access Token: {tokenResponse.AccessToken}");
                    verifySuccess = true;
                }
            }

            //call API Resource - 訪問API資源
            HttpClient apiClient = new HttpClient();
            apiClient.SetBearerToken(tokenResponse?.AccessToken);
            HttpResponseMessage response = await apiClient.GetAsync("http://localhost:6000/weatherforecast");
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"API Request Error, StatusCode is : {response.StatusCode}");
            }
            else
            {
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine("");
                Console.WriteLine($"Result: {JArray.Parse(content)}");
            }

            Console.ReadKey();
        }
    }
}

用戶名密碼錯誤的話,會一直提示重新輸入

我們使用用戶名密碼 alice / alice 進行登錄

可以看到,成功獲取到AccessToken,並使用AccessToken訪問到受保護的API獲取到結果。

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