第十二節:Ocelot集成IDS4認證授權-微服務主體架構完成


一. 前言

1.業務背景

  我們前面嘗試了在業務服務器上加IDS4校驗,實際上是不合理的, 在生產環境中,業務服務器會有很多個,如果把校驗加在每個業務服務器上,代碼冗余且不易維護(很多情況下業務服務器不直接對外開放),所以我們通常把校驗加在Ocelot網關上,也就是說校驗通過了,Ocelot網關才會把請求轉發給相應的業務服務器上.(我們這里通常是網關和業務服務器在一個內網中,業務服務器不開放外網)

(和前面:Jwt配合中間件校驗流程上是一樣的,只不過這里的認證和授權都基於IDS4來做)

PS:關於IDS4服務器,可以配置在網關后面,通過網關轉發;

   也可以不經網關轉發,單獨存在, 這里要說明的是,如果經過網關轉發,那么對於IDS4而言,只是單純的轉發,不走Ocelot上的校驗,其實也很簡單,就是不配置AuthenticationProviderKey即可.

 

 

2.用到的項目

(1).Case2下的GateWay_Server :網關服務器

(2).Case2下的ID4_Server:認證+授權服務器

(3).GoodsService + OrderService:二者都是資源服務器

(4).PostMan:充當客戶端(即第三方應用)

(5).MyClient2:用控制台充當客戶端(即第三方應用)

(6).Consul:網關Ocelot已經集成Consul服務發現了,而且資源服務器也已經注冊到Consul中了.

 

二. 核心剖析和測試

1.搭建步驟

(一).啟動資源服務器

 (1).啟動Consul:【consul.exe agent -dev】

 (2).啟動資源服務器:【dotnet GoodsService.dll --urls="http://*:7001" --ip="127.0.0.1" --port=7001 】

            【dotnet OrderService.dll --urls="http://*:7004" --ip="127.0.0.1" --port=7004 】

代碼分享:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class CatalogController : ControllerBase
    {
        [HttpGet]
        public string GetGoodById1(int id)
        {
            var myData = new
            {
                status = "ok",
                goods = new Goods()
                {
                    id = id,
                    goodName = "apple",
                    goodPrice = 6000,
                    addTime = DateTime.Now
                }
            };
            var jsonData = JsonHelp.ToJsonString(myData);
            return jsonData;   //返回前端的數據不能直接點出來
        }
    }
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class BuyController : ControllerBase
    {

        [HttpPost]
        public string pOrder1()
        {
            return "ok";
        }
    }

(二). GateWay_Server網關的基本配置

(1).Nuget安裝包【Ocelot 16.0.1】【Ocelot.Provider.Consul 16.0.1】, 並在ConfigureService和Config中進行配置 services.AddOcelot().AddConsul(); 和 app.UseOcelot().Wait();

(2).Nuget安裝包【IdentityServer4.AccessTokenValidation 3.0.1】,用於身份校驗.

(3).編寫配置文件(OcelotConfig.json),屬性改為始終復制,在Program中進行加載;

 在配置文件,給GoodsService和OrderService下的節點, 添加 "AuthenticationProviderKey": "OrderServiceKey"/"GoodsServiceKey", 和ConfigureService中的注冊進行對應,表示該轉發需要走校驗.

 (把IDS4獲取Token的地址也配置進來,但不做校驗,也可以不配置進來)

代碼分享

//模式三:將Ocelot與consul結合處理,在consul中已經注冊業務服務器地址,在Ocelot端不需要再注冊了(推薦用法)
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "GoodsService", //Consul中的服務名稱
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //輪詢算法:依次調用在consul中注冊的服務器
      },
      "UseServiceDiscovery": true, //啟用服務發現(可以省略,因為會默認賦值)
      "UpstreamPathTemplate": "/GoodsService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "GoodsServiceKey",
        "AllowedScopes": []
      }
    },
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "LeastConnection" //最小連接數算法
      },
      "UseServiceDiscovery": true,
      "UpstreamPathTemplate": "/OrderService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "OrderServiceKey",
        "AllowedScopes": []
      }
    },
    //把ID4_Server認證授權服務器也配置進來,但它不再Ocelot層次上加密,單純的進行轉發
    {
      //轉發下游(業務服務器)的匹配規則
      "DownstreamPathTemplate": "/{url}",
      //下游請求類型
      "DownstreamScheme": "http",
      //下游的ip和端口,和上面的DownstreamPathTemplate匹配起來
      "DownstreamHostAndPorts": [
        {
          "Host": "127.0.0.1",
          "Port": 7051
        }
      ],
      //上游(即Ocelot)接收規則
      "UpstreamPathTemplate": "/auth/{url}",
      //上游接收請求類型
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ],
  //下面是配置Consul的地址和端口
  "GlobalConfiguration": {
    //對應Consul的ip和Port(可以省略,因為會默認賦值)
    "ServiceDiscoveryProvider": {
      "Host": "127.0.0.1",
      "Port": 8500
    }
  }
}
View Code

(4). 在ConfigureServices注冊ID4校驗,詳細參數見代碼說明   特別注意:ApiName必須對應Id4中配置的

代碼分享:

  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.注冊Ocelot
            services.AddOcelot().AddConsul();

            //2.注冊ID4校驗
            services.AddAuthentication("Bearer")
                    .AddIdentityServerAuthentication("GoodsServiceKey", option =>      //這里GoodsServiceKey與Ocelot配置文件中的AuthenticationProviderKey對應,從而進行綁定驗證
                    {
                        option.Authority = "http://127.0.0.1:7051";  //這里配置是127.0.0.1,那么通過ID4服務器獲取token的時候,就必須寫127.0.0.1,不能寫localhost.   
                        option.ApiName = "GoodsService";             //必須對應ID4服務器中GetApiResources配置的apiName,此處不能隨便寫!!
                        option.RequireHttpsMetadata = false;
                    })
                    .AddIdentityServerAuthentication("OrderServiceKey", option =>
                    {
                        option.Authority = "http://127.0.0.1:7051";
                        option.ApiName = "OrderService";
                        option.RequireHttpsMetadata = false;
                    });

            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();

            //啟用Ocelot
            app.UseOcelot().Wait();


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

(5).配置IP+端口的命令行啟動 【dotnet GateWay_Server.dll --urls="http://*:7050" --ip="127.0.0.1" --port=7050 】

(三). ID4_Server的基本配置

(1). Nuget安裝包【IdentityServer4    4.0.2】

(2). 在ConfigureServie注冊客戶端模式 或 用戶名密碼模式,根據需要開啟或注釋哪個, Config中啟用IDS4

配置文件-客戶端模式

/// <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")
             };
        }


        /// <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" } });
            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" }        //允許訪問的api服務
                },
                new Client
                {
                    ClientId = "client2",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService"}        //允許訪問的api服務
                },
                 new Client
                {
                    ClientId = "client3",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ClientCredentials, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0003".Sha256())},    //密鑰和加密方式
                    AllowedScopes = {"OrderService" }        //允許訪問的api服務
                }
            };
            return clients;
        }
    }
View Code

配置文件-用戶名密碼模式

 /// <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")
             };
        }


        /// <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" } });
            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" }        //允許訪問的api服務
                },
                new Client
                {
                    ClientId = "client2",//客戶端ID                             
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //驗證類型:客戶端驗證
                    ClientSecrets ={ new Secret("0002".Sha256())},    //密鑰和加密方式
                    AllowedScopes = { "GoodsService" }        //允許訪問的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)
        {

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


            //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();

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

            app.UseAuthorization();

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

(3). 配置IP+端口的命令行啟動 【dotnet ID4_Server.dll --urls="http://*:7051" --ip="127.0.0.1" --port=7051 】

 

2. 用PostMan測試

場景1:用PostMan進行下面測試

  測試Get請求:http://127.0.0.1:7050/GoodsService/Catalog/GetGoodById1?id=123 測試結果:401未授權

  測試Post請求:http://127.0.0.1:7050/OrderService/Buy/pOrder1 測試結果:401未授權

測試結果:

 

場景2:用PostMan進行下面測試

  先請求:http://127.0.0.1:7051/connect/token 表單參數如下,獲取token值

  client_id=client1

  grant_type=client_credentials

  client_secret=0001

  然后再Header中要配置 token=Bear xxxxxxxx(上面獲取的token值),

(也可用PostMan中Authorization選項卡下,TYPE選擇Bearer Token,然后內容直接輸入上面獲取的token即可)

測試Get請求:http://127.0.0.1:7050/GoodsService/Catalog/GetGoodById1?id=123 測試結果:測試通過,獲得返回值

測試Post請求:http://127.0.0.1:7050/OrderService/Buy/pOrder1 測試結果:測試通過,獲得返回值

PS: 上述場景2的測試,是直接請求的IDS4服務器,並沒有通過Ocelot轉發哦,當然也可以請求 http://127.0.0.1:7050/auth/connect/token"來獲取(本質上是通過Ocelot轉發到了 http://127.0.0.1:7051/connect/token)

測試結果: 

 

 

3.用控制台客戶端測試

公用代碼

            //認證服務器地址
            string rzAddress = "http://127.0.0.1:7051";
            //通過Ocelot轉發到認證服務器地址
            string ocelotRzAddress = "http://127.0.0.1:7050/auth";
            //資源服務器1api地址
            string resAddress1 = "http://127.0.0.1:7050/GoodsService/Catalog/GetGoodById1?id=123";
            //資源服務器2api地址
            string resAddress2 = "http://127.0.0.1:7050/OrderService/Buy/pOrder1 ";

(1).客戶端模式(直接請求IDS4服務器)

代碼分享

  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://xxxx/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】
                //2.1 向資源服務器1發送請求
                var response = await apiClient.GetAsync(resAddress1);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器1的結果為:{content}");
                }
                //2.2 向資源服務器2發送請求
                var sContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var response2 = await apiClient.PostAsync(resAddress2, sContent);
                if (!response2.IsSuccessStatusCode)
                {
                    Console.WriteLine(response2.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response2.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器2的結果為:{content}");
                }
View Code

運行結果

 

(2).客戶端模式(通過Ocelot轉發到IDS4服務器)

代碼分享

    var client = new HttpClient();
                //向認證服務器發送請求,要求獲得令牌
                Console.WriteLine("---------------------------- 一.向認證服務器發送請求,要求獲得令牌-----------------------------------");
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    Address = ocelotRzAddress + "/connect/token",
                    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】
                //2.1 向資源服務器1發送請求
                var response = await apiClient.GetAsync(resAddress1);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器1的結果為:{content}");
                }
                //2.2 向資源服務器2發送請求
                var sContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var response2 = await apiClient.PostAsync(resAddress2, sContent);
                if (!response2.IsSuccessStatusCode)
                {
                    Console.WriteLine(response2.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response2.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器2的結果為:{content}");
                }
View Code

運行結果

 

(3).用戶名密碼模式(直接請求IDS服務器)

代碼分享

      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】
                //2.1 向資源服務器1發送請求
                var response = await apiClient.GetAsync(resAddress1);
                if (!response.IsSuccessStatusCode)
                {
                    Console.WriteLine(response.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器1的結果為:{content}");
                }
                //2.2 向資源服務器2發送請求
                var sContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var response2 = await apiClient.PostAsync(resAddress2, sContent);
                if (!response2.IsSuccessStatusCode)
                {
                    Console.WriteLine(response2.StatusCode);
                    Console.ReadKey();
                }
                else
                {
                    var content = await response2.Content.ReadAsStringAsync();
                    Console.WriteLine($"請求資源服務器2的結果為:{content}");
                }
View Code

運行結果

 

 

 

 

 

 

 

 

!

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

 


免責聲明!

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



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