第九節:IdentityServer4簡介及客戶端模式和用戶密碼模式的應用


一. IDS4簡介

1. 什么是IDS4

  IdentityServer是基於OpenID Connect協議標准的身份認證和授權程序,它實現了OpenID 和 OAuth 2.0 協議。

2. 相關地址

 (1).官網:https://identityserver4.readthedocs.io/en/latest/

 (2).GitHub地址:https://github.com/IdentityServer

 (2).三方中文文檔:http://www.identityserver.com.cn/Home/Detail/Zhengtizongshu

3. IDS4有哪些功能

 (1).身份認證服務(官方認證的OpenID Connect實現)

 (2).單點登錄與注銷(SSO)

 (3).訪問受控的Api

4. IDS4的內部概念

(1).用戶-User

  使用已注冊的客戶端(指在id4中已經注冊)訪問資源的人。

(2).客戶端-Client (即第三方應用)

  客戶端就是從identityserver請求令牌的軟件(你可以理解為一個app即可),既可以通過身份認證令牌來驗證識別用戶身份,又可以通過授權令牌來訪問服務端的資源。但是客戶端首先必須在申請令牌前已經在identityserver服務中注冊過。實際客戶端不僅可以是Web應用程序,app或桌面應用程序(你就理解為pc端的軟件即可),SPA,服務器進程等。

(3).資源-Resources

  資源就是你想用identityserver保護的東西,可以是用戶的身份數據或者api資源。每一個資源都有一個唯一的名稱,客戶端使用這個唯一的名稱來確定想訪問哪一個資源(在訪問之前,實際identityserver服務端已經配置好了哪個客戶端可以訪問哪個資源,所以你不必理解為客戶端只要指定名稱他們就可以隨便訪問任何一個資源)

(4).身份令牌(d_token jwt)

  一個身份令牌指的就是對認證過程的描述。它至少要標識某個用戶(Called the sub aka subject claim)的主身份信息,和該用戶的認證時間和認證方式。但是身份令牌可以包含額外的身份數據,具體開發者可以自行設定,但是一般情況為了確保數據傳輸的效率,開發者一般不做過多額外的設置,大家也可以根據使用場景自行決定。

(5).訪問令牌(access_token oauth 2.0)

  訪問令牌允許客戶端訪問某個 API 資源。客戶端請求到訪問令牌,然后使用這個令牌來訪問 API資源。訪問令牌包含了客戶端和用戶(如果有的話,這取決於業務是否需要,但通常不必要)的相關信息,API通過這些令牌信息來授予客戶端的數據訪問權限。

5. IDS4的幾種模式

 (1).客戶端模式:ClientCredentials

 (2).用戶名密碼模式:ResourceOwnerPassword

 (3).隱式模式(簡化模式):implicit

 (4).授權碼模式:Code

除此之外,還有多種模式共存,比如: 客戶端+用戶名密碼模式  ResourceOwnerPasswordAndClientCredentials

 

二. 客戶端模式

一. 模式深究

1. 含義

  指【客戶端(也就是 第三方應用)】以自己的名義,而不是以用戶的名義,向【服務提供商】的【認證服務器】進行認證。嚴格地說,客戶端模式並不屬於OAuth2.0 框架所要解決的問題。在這種模式中,【用戶】直接向【客戶端】注冊,【客戶端】以自己的名義要求【服務提供商】提供服務,其實不存在授權問題。

2. 運行流程

 

步驟:

A. 客戶端(第三方應用)向認證服務器發送請求進行身份認證,並要求得到一個訪問令牌Token。

B. 認證服務器確認無誤后(認證),向客戶端頒發訪問令牌Token(授權)。

C. 客戶端拿到訪問令牌(token),向資源服務器發送請求,獲取需要的信息。(上圖中沒有畫)

二. 基於IDS4代碼實操

1. 項目環境准備

 (1). MyClient1:客戶端控制台程序。

 (2). PostMan工具:也充當客戶端程序。

 (3). ID4Test:認證+授權服務器.

 (4). ProductService:資源服務器.

PS.幾種授權模式都會產生token,這里我單純的想使用一下token,那么我直接給業務服務器ProductService中的api接口增加校驗,這個校驗直接加在業務服務器上,僅僅是為了測試。(后面介紹加在Ocelot上)

2.搭建測試步驟

 (一). IDS4認證授權服務器的配置

 (1).通過Nuget給ID4Test安裝【IdentityServer4 4.0.2】

 (2).新建Config1配置類,包括兩個方法:GetApiResources 和 GetClients. 其中GetApiResources里包含需要保護的Api業務服務器名稱,GetClients里包含了哪些客戶端資源可以訪問,其中可以通過AllowedScopes = { "GoodsService", "ProductService" } 來授權哪個客戶端能訪問哪些api資源,例外還要配置 ClientId、校驗方式(GrantTypes.ClientCredentials)、密鑰

 (3).Startup中的ConfigureService和Config的配置,如代碼所示.

 (4).通過命令啟動項目【dotnet ID4Test.dll --urls="http://*:7040" --ip="127.0.0.1" --port=7040 】

配置類:

    /// <summary>
    /// 客戶端模式
    /// </summary>
    public class Config1
    {
        /// <summary>
        /// 配置Api范圍集合
        /// 4.x版本新增的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            return new List<ApiScope>
            {
                new ApiScope("GoodsService"),
                new ApiScope("OrderService"),
                new ApiScope("ProductService")
             };
        }


        /// <summary>
        /// 需要保護的Api資源
        /// 4.x版本新增后續Scopes的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            List<ApiResource> resources = new List<ApiResource>();
            //ApiResource第一個參數是ServiceName,第二個參數是描述
            resources.Add(new ApiResource("GoodsService", "GoodsService服務需要保護哦") { Scopes = { "GoodsService" } });
            resources.Add(new ApiResource("OrderService", "OrderService服務需要保護哦") { Scopes = { "OrderService" } });
            resources.Add(new ApiResource("ProductService", "ProductService服務需要保護哦") { Scopes = { "ProductService" } });
            return resources;
        }


        /// <summary>
        /// 可以使用ID4 Server 客戶端資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            List<Client> clients = new List<Client>() {
                new Client
                {
                    ClientId = "client1",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0001".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService", "OrderService", "ProductService" }        //允許訪問的api服務
                },
                new Client
                {
                    ClientId = "client2",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService" , "ProductService" } //允許訪問的api服務
                },
                 new Client
                {
                    ClientId = "client3",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0003".Sha256())},    //密鑰和加密方式
                    AllowedScopes = {"OrderService" }        //允許訪問的api服務
                },
                new Client
                {
                    ClientId = "client4",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0004".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "ProductService" },        //允許訪問的api服務
                    //基於角色授權
                   Claims=
                    {
                        new ClientClaim("role","ypfRole")
                    },
                    ClientClaimsPrefix="", //把前綴設置成空,就IDS4和Core MVC之間就不用轉換了
                }
            };
            return clients;
        }
    }
View Code

Startup類:

  public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //1. 客戶端模式
            services.AddIdentityServer()
                  .AddDeveloperSigningCredential()    //生成Token簽名需要的公鑰和私鑰,存儲在bin下tempkey.rsa(生產場景要用真實證書,此處改為AddSigningCredential)
                  .AddInMemoryApiResources(Config1.GetApiResources())  //存儲需要保護api資源
                  .AddInMemoryApiScopes(Config1.GetApiScopes())        //配置api范圍 4.x版本必須配置的
                  .AddInMemoryClients(Config1.GetClients()); //存儲客戶端模式(即哪些客戶端可以用)

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseAuthorization();

            //1.啟用IdentityServe4
            app.UseIdentityServer();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

(二). ProductService資源服務器

 (1).通過Nuget給ProductService安裝 【IdentityServer4.AccessTokenValidation 3.0.1】

 (2).在ConfigureService通過AddIdentityServerAuthentication連接ID4服務器,進行校驗。這里ApiName中的“ProductService”必須是ID4中GetApiResources中添加的。

特別注意:這個Authority要用127.0.0.1, 不用Localhost,因為我們獲取token的時候,使用的地址也是127.0.0.1,必須對應起來.

 (3).在Config中添加認證中間件 app.UseAuthentication();

 (4).新建一個GetMsg接口,並加上特性[Authorize]。

 (5).配置命令行ip+port啟動,通過指令【dotnet ProductService.dll --urls="http://*:7007" --ip="127.0.0.1" --port=7007】啟動

控制器代碼:

 [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {

        /// <summary>
        /// 資源服務器的api
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpGet]
        public string GetMsg()
        {
            //快速獲取token的方式
            string token = HttpContext.GetTokenAsync("access_token").Result;

            return $"ypf";
        }


    }
View Code

Startup類:

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            //校驗AccessToken,從身份校驗中心(ID4Test)進行校驗
            services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)  //Bear模式
                    .AddIdentityServerAuthentication(options =>
                    {
                        options.Authority = "http://127.0.0.1:7040"; // 1、授權中心地址
                        options.ApiName = "ProductService"; // 2、api名稱(項目具體名稱)
                        options.RequireHttpsMetadata = false; // 3、https元數據,不需要
                    });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();


            //認證中間件(服務於上ID4校驗,一定要放在UseAuthorization之前)
            app.UseAuthentication();

            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

 (三).用PostMan充當客戶端測試

場景1.

 用PostMan直接發送Get請求,請求地址為:http://127.0.0.1:7007/api/Home/GetMsg ,測試結果:報401未授權

場景2.

 (1).用PostMan發送Post請求,http://127.0.0.1:7040/connect/token 獲取token (這里用客戶端模式進行測試),表單(form-data)參數如下:

  client_id=client1

  grant_type=client_credentials

  client_secret=0001

 請求成功,返回token值和過期時間。

注:/connect/token 是IDS4內部封裝地址

 (2).用PostMan繼續發送Get請求,請求地址為:http://127.0.0.1:7007/api/Home/GetMsg, 其中Header中要配置 token:Bear xxxxxxxx(B中獲取的token值),(也可用PostMan中Authorization選項卡下,TYPE選擇Bearer Token,然后內容直接輸入B中獲取的token即可)

 (3).測試結果:請求成功,返回“ypf”.

運行結果:

 (四).用控制台充當客戶端測試

 (1).首先通過Nuget安裝【IdentityModel 4.3.1】,可以擴展HttpClient請求

 (2).利用GetDiscoveryDocumentAsync方法可以獲取更多的服務地址,比如 disco.TokenEndpoint 即/connect/token

 (3).攜帶 ClientId、ClientSecret,利用RequestClientCredentialsTokenAsync方法,向認證服務器發送請求,獲取token

 (4).客戶端拿到token利用SetBearerToken方法,可以設置 token:Bear xxxxxxxx的格式,向資源服務器發送請求.

 (5).請求成功,獲取結果.

PS:這里是為了演示,使用HttpClient,正式環境建議使用HttpClientFactory

控制台代碼:

             //認證服務器地址
            string rzAddress = "http://127.0.0.1:7040";
            //資源服務器api地址
            string resAddress = "http://127.0.0.1:7007/api/Home/GetMsg";
            //資源服務器api地址(基於角色授權)
            string resAddress2 = "http://127.0.0.1:7007/api/Home/GetMsg2";

            #region 一.客戶端模式
            {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    return;
                }
                //向認證服務器發送請求,要求獲得令牌
                Console.WriteLine("---------------------------- 一.向認證服務器發送請求,要求獲得令牌-----------------------------------");
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    //在上面的地址上拼接:/connect/token,最終:http://127.0.0.1:7040/connect/token
                    Address = disco.TokenEndpoint,
                    ClientId = "client1",
                    ClientSecret = "0001",
                    //空格分隔的請求范圍列表,省略的話是默認配置的所有api資源,如: client1對應的是:{ "GoodsService", "OrderService", "ProductService" }  
                    //這里填寫的范圍可以和配置的相同或者比配置的少,比如{ "GoodsService OrderService"},這里只是一個范圍列表,並不是請求哪個api資源必須要  寫在里面
                    //但是如果配置的和默認配置出現不同,則認證不能通過 比如{ "ProductService OrderService111"},
                    //綜上所述:此處可以不必配置
                    //Scope = "ProductService OrderService111"
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"認證錯誤:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);

                //攜帶token向資源服務器發送請求
                Console.WriteLine("----------------------------二.攜帶token向資源服務器發送請求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);   //設置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器的結果為:{content}");
                }
            }
            #endregion
View Code

運行結果:

3. 補充基於角色授權

 在上述基礎上,增加客戶端client4, 並配置角色授權 new Claim("role","ypfRole"),這里清空一下前綴哦,新增GetMsg2接口,添加[Authorize(Roles = "ypfRole")] ,角色授權用客戶端進行測試,只有client4才能訪問GetMsg2接口。

控制器代碼:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {

        [Authorize(Roles = "ypfRole")]  //角色授權
        [HttpGet]
        public string GetMsg2()
        {
            return "ypf2";
        }
    }
View Code

客戶端控制台代碼:

            //認證服務器地址
            string rzAddress = "http://127.0.0.1:7040";
            //資源服務器api地址(基於角色授權)
            string resAddress2 = "http://127.0.0.1:7007/api/Home/GetMsg2";        

 {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    Console.ReadKey();
                }
                //向認證服務器發送請求,要求獲得令牌
                Console.WriteLine("---------------------------- 一.向認證服務器發送請求,要求獲得令牌-----------------------------------");
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    //在上面的地址上拼接:/connect/token,最終:http://127.0.0.1:7040/connect/token
                    Address = disco.TokenEndpoint,
                    ClientId = "client4",
                    ClientSecret = "0004",
                    //空格分隔的請求范圍列表,省略的話是默認配置的所有api資源,如: client1對應的是:{ "GoodsService", "OrderService", "ProductService" }  
                    //這里填寫的范圍可以和配置的相同或者比配置的少,比如{ "GoodsService OrderService"},這里只是一個范圍列表,並不是請求哪個api資源必須要  寫在里面
                    //但是如果配置的和默認配置出現不同,則認證不能通過 比如{ "ProductService OrderService111"},
                    //綜上所述:此處可以不必配置
                    //Scope = "ProductService OrderService111"
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"認證錯誤:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);

                //攜帶token向資源服務器發送請求
                Console.WriteLine("----------------------------二.攜帶token向資源服務器發送請求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);   //設置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress2);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine($"請求報錯:{response.StatusCode}");
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器的結果為:{content}");
                }
            }
View Code

運行結果:

 

三. 用戶名密碼模式

一. 模式深究

(1). 含義:

  用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要授權。

  在這種模式中,用戶必須把自己的賬號和密碼給客戶端,但是客戶端不得儲存密碼。這通常用在用戶對客戶端高度信任的情況下,比如客戶端是操作系統的一部分,或者由一個著名公司出品。而認證服務器只有在其他授權模式無法執行的情況下,才能考慮使用這種模式。

(2). 運行流程:

步驟:

A. 用戶向客戶端提供用戶名和密碼。

B. 客戶端將用戶名和密碼發給認證服務器,並要求得到一個訪問令牌Token。

C. 認證服務器確認無誤后(認證),向客戶端頒發訪問令牌Token(授權)。

D. 客戶端拿到訪問令牌(token),向資源服務器發送請求,獲取需要的信息。(上圖中沒有畫)

二. 基於IDS4代碼實操

1.項目環境准備

 (1). MyClient1:客戶端控制台程序。

 (2). PostMan工具:也充當客戶端程序。

 (3). ID4Test:認證+授權服務器.

 (4). ProductService:資源服務器.

PS.幾種授權模式都會產生token,現在我單純的想使用一下token,那么我直接給業務服務器ProductService中的api接口增加校驗,這個校驗直接加在業務服務器上,僅僅是為了測試。(后面介紹加在Ocelot上)。

2.搭建測試步驟

 (一). IDS4認證授權服務器的配置

 (1).通過Nuget給ID4Test安裝【IdentityServer4 4.0.2】

 (2).新建Config2配置類,包括兩個方法:GetApiResources 、 GetClients、GetUsers. 其中GetApiResources里包含需要保護的Api業務服務器名稱,GetClients里包含了哪些客戶端資源可以訪問,其中可以通過AllowedScopes = { "GoodsService", "OrderService" } 來授權哪個客戶端能訪問哪些api資源,例外還要配置 ClientId、校驗方式(GrantTypes.ResourceOwnerPassword)、密鑰。GetUsers里包含了哪些用戶名和密碼可以訪問

 (3).Startup中的ConfigureService和Config的配置,如代碼所示. 多了AddTestUsers(Config2.GetUsers().ToList())

 (4).通過命令啟動項目【dotnet ID4Test.dll --urls="http://*:7040" --ip="127.0.0.1" --port=7040 】

配置類:

  /// <summary>
    /// 用戶名密碼模式
    /// </summary>
    public class Config2
    {
        /// <summary>
        /// 配置Api范圍集合
        /// 4.x版本新增的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            return new List<ApiScope>
            {
                new ApiScope("GoodsService"),
                new ApiScope("OrderService"),
                new ApiScope("ProductService")
             };
        }


        /// <summary>
        /// 需要保護的Api資源
        /// 4.x版本新增后續Scopes的配置
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            List<ApiResource> resources = new List<ApiResource>();
            //ApiResource第一個參數是ServiceName,第二個參數是描述
            resources.Add(new ApiResource("GoodsService", "GoodsService服務需要保護哦") { Scopes = { "GoodsService" } });
            resources.Add(new ApiResource("OrderService", "OrderService服務需要保護哦") { Scopes = { "OrderService" } });
            resources.Add(new ApiResource("ProductService", "ProductService服務需要保護哦") { Scopes = { "ProductService" } });
            return resources;
        }


        /// <summary>
        /// 可以使用ID4 Server 客戶端資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            List<Client> clients = new List<Client>() {
                new Client
                {
                    ClientId = "client1",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0001".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService", "OrderService", "ProductService" }        //允許訪問的api服務
                },
                new Client
                {
                    ClientId = "client2",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService", "ProductService" }        //允許訪問的api服務
                },
                 new Client
                {
                    ClientId = "client3",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, //驗證類型:用戶名密碼模式 和 客戶端模式
                    ClientSecrets ={ new Secret("0003".Sha256())},    //密鑰和加密方式
                    AllowedScopes = {"OrderService" }        //允許訪問的api服務
                }
            };
            return clients;
        }

        /// <summary>
        /// 定義可以使用ID4的用戶資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> GetUsers()
        {
            return new List<TestUser>()
            {
                new TestUser
                {
                    SubjectId = "10001",
                    Username = "ypf1",     //賬號
                    Password = "ypf001"    //密碼
                },
                new TestUser
                {
                    SubjectId = "10002",
                    Username = "ypf2",
                    Password = "ypf002"
                },
                new TestUser
                {
                    SubjectId = "10003",
                    Username = "ypf3",
                    Password = "ypf003"
                }
            };
        }
    }
View Code

Startup類:

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //2. 用戶名密碼模式
            services.AddIdentityServer()
                  .AddDeveloperSigningCredential()    //生成Token簽名需要的公鑰和私鑰,存儲在bin下tempkey.rsa(生產場景要用真實證書,此處改為AddSigningCredential)
                  .AddInMemoryApiResources(Config2.GetApiResources())  //存儲需要保護api資源
                  .AddInMemoryClients(Config2.GetClients()) //存儲客戶端模式(即哪些客戶端可以用)
                  .AddInMemoryApiScopes(Config1.GetApiScopes())        //配置api范圍 4.x版本必須配置的
                  .AddTestUsers(Config2.GetUsers().ToList());  //存儲哪些用戶、密碼可以訪問 (用戶名密碼模式)

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseAuthorization();

            //1.啟用IdentityServe4
            app.UseIdentityServer();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
View Code

(二). ProductService資源服務器

 (1).通過Nuget給ProductService安裝 【IdentityServer4.AccessTokenValidation 3.0.1】

 (2).在ConfigureService通過AddIdentityServerAuthentication連接ID4服務器,進行校驗。這里ApiName中的“ProductService”必須是ID4中GetApiResources中添加的。

特別注意:這個Authority要用127.0.0.1, 不用Localhost,因為我們獲取token的時候,使用的地址也是127.0.0.1,必須對應起來。

 (3).在Config中添加認證中間件 app.UseAuthentication();

 (4).新建一個GetMsg接口,並加上特性[Authorize]。

 (5).配置命令行ip+port啟動,通過指令【dotnet ProductService.dll --urls="http://*:7007" --ip="127.0.0.1" --port=7007】啟動

代碼同客戶端模式

 (三).用PostMan充當客戶端測試

 場景1.

  用PostMan直接發送Get請求,請求地址為:http://127.0.0.1:7007/api/Home/GetMsg ,測試結果:報401未授權

 場景2.

  (1).用PostMan發送Post請求,http://127.0.0.1:7040/connect/token,用Post請求,表單提交(form-data)的方式進行測試,表單參數如下:

    client_id=client1

    grant_type=password (此處和上面的不一樣哦)

    client_secret=0001

    username=ypf1

    password=ypf001

  請求成功,返回token值和過期時間。 client_id和client_secret也可以改為 client2和0002,如果不正確,則檢驗不通過。

注:/connect/token 是ID4內部封裝地址。

 (2).用PostMan繼續發送Get請求,請求地址為:http://127.0.0.1:7007/api/Home/GetMsg, 其中Header中要配置 token:Bear xxxxxxxx(B中獲取的token值),(也可用PostMan中Authorization選項卡下,TYPE選擇Bearer Token,然后內容直接輸入B中獲取的token即可)

 (3).測試結果:請求成功,返回“ypf”.

運行結果:

 

(四).用控制台充當客戶端測試

 (1).首先通過Nuget安裝【IdentityModel 4.3.1】,可以擴展HttpClient請求

 (2).利用GetDiscoveryDocumentAsync方法可以獲取更多的服務地址,比如 disco.TokenEndpoint 即/connect/token

 (3).攜帶 ClientId、ClientSecret、UserName、Password,利用RequestPasswordTokenAsync方法,向認證服務器發送請求,獲取token

 (4).客戶端拿到token利用SetBearerToken方法,可以設置 token:Bear xxxxxxxx的格式,向資源服務器發送請求.

 (5).請求成功,獲取結果.

PS:這里是為了演示,使用HttpClient,正式環境建議使用HttpClientFactory。

客戶端代碼:

           //認證服務器地址
            string rzAddress = "http://127.0.0.1:7040";
            //資源服務器api地址
            string resAddress = "http://127.0.0.1:7007/api/Home/GetMsg";
 {
                var client = new HttpClient();
                var disco = await client.GetDiscoveryDocumentAsync(rzAddress);
                if (disco.IsError)
                {
                    Console.WriteLine(disco.Error);
                    Console.ReadKey();
                }
                //向認證服務器發送請求,要求獲得令牌
                Console.WriteLine("---------------------------- 一.向認證服務器發送請求,要求獲得令牌-----------------------------------");
                var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "client1",
                    ClientSecret = "0001",
                    UserName = "ypf2",
                    Password = "ypf002"
                    //Scope = ""   //可以不用配置
                });
                if (tokenResponse.IsError)
                {
                    Console.WriteLine($"認證錯誤:{tokenResponse.Error}");
                    Console.ReadKey();
                }
                Console.WriteLine(tokenResponse.Json);
                //攜帶token向資源服務器發送請求
                Console.WriteLine("----------------------------二.攜帶token向資源服務器發送請求-----------------------------------");
                var apiClient = new HttpClient();
                apiClient.SetBearerToken(tokenResponse.AccessToken);    //設置Token格式  【Bear xxxxxx】
                var response = await apiClient.GetAsync(resAddress);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器的結果為:{content}");
                }
            }
View Code

運行結果:

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM