Ocelot網關+IdentityServer4實現API權限認證


Ocelot是一個用.NET Core實現並且開源的API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷、並內置了負載均衡器與Service Fabric、Butterfly Tracing集成。這些功能只都只需要簡單的配置即可完成。

本文主要向大家簡單介紹一下如何結合Ocelot網關和IdentityServer4鑒權服務實現API接口權限認證。關於IdentityServer4大家可以看下我之前的文章。

好了,下面開始進入正題。我們需要搭建兩個API項目+一個IdentityServer4鑒權服務+一個Ocelot網關服務。本文以.NetCore2.2為例。

第一步,快速搭建兩個WebAPI項目。

 1.新建第一個WebApi項目:

 

2.配置API端口:6000

  1)配置文件appsettings.json中增加端口配置節點。

{
  "Http": {
    "Port": 6000
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

  2)主程序Program.cs中添加端口監聽:

 1 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
 2             WebHost.CreateDefaultBuilder(args)
 3                 .ConfigureKestrel(options =>
 4                 {
 5                     //監聽端口
 6                     var config = options.ApplicationServices.GetRequiredService<IConfiguration>();
 7                     var port = config.GetValue<int>("Http:Port");
 8                     options.ListenAnyIP(port);
 9                 })
10                 .UseStartup<Startup>();

3.啟動項目

 

4.新建第二個WebAPI2項目,操作步驟同上,監聽端口6002。

 

第二步,搭建IdentityServer4鑒權服務

1.新增項目Identity4

  2.添加IdentityServer4 Nuget程序包。版本大家根據實際開發環境選擇。

 3.添加IdentityServer配置類

public class Config
    {
        /// <summary>
        /// 定義API資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1","測試API"),
                new ApiResource("api2","測試API2")
            };
        }

        /// <summary>
        /// 定義客戶端
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client{
                    ClientId="client",
                    //授權方式為客戶端驗證,類型可參考GrantTypes枚舉
                    AllowedGrantTypes=GrantTypes.ClientCredentials,
                    //秘鑰
                    ClientSecrets=
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes=new []{ "api1" }
                 },
                new Client{
                    ClientId="client2",
                    //授權方式為用戶密碼驗證,類型可參考GrantTypes枚舉
                    AllowedGrantTypes=GrantTypes.ResourceOwnerPassword,
                    //秘鑰
                    ClientSecrets=
                    {
                        new Secret("secret2".Sha256())
                    },
                    AllowedScopes=new []{ "api2", IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile }
                 }
            };
        }

        /// <summary>
        /// 定義身份資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
             };
        }
    }

這里我們定義了兩個API資源(就是我們上面創建的兩個API項目):

    a.第一個api我們授權client以客戶端模式訪問

    b.第二個api我們授權client2以用戶密碼模式訪問

 4.針對用戶密碼訪問模式,我們這里使用了自定義用戶認證。(數據庫用戶密碼校驗)

 我們實現接口:IResourceOwnerPasswordValidator,並通過實現接口方法ValidateAsyn()完成用戶認證。

 數據庫訪問我這里使用的SqlSugar ORM框架,在這里不多做介紹,感興趣的同學可以去了解一下。

public class UserPasswordValidator : IResourceOwnerPasswordValidator
    {
        private readonly IDBContext dbcontext;
        public UserPasswordValidator(IDBContext _context)
        {
            dbcontext = _context;
        }
        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
//通過sqlsugar ORM框架實現數據庫訪問
var user = await dbcontext.DB.Queryable<User>().Where(x => x.USER_NAME == context.UserName && x.PASSWORD == context.Password).FirstAsync(); if (user != null) { context.Result = new GrantValidationResult(subject: context.UserName, authenticationMethod: GrantType.ResourceOwnerPassword, claims: GetUserClaims(user)); } else context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "賬號或密碼錯誤"); } /// <summary> /// 獲取用戶聲明項 /// </summary> /// <returns></returns> private List<Claim> GetUserClaims(User user) { List<Claim> list = new List<Claim>(); list.Add(new Claim(JwtClaimTypes.Name, user.USER_NAME)); list.Add(new Claim(JwtClaimTypes.Id, user.USER_ID));
return list; } }

5.注冊IdentityServer4服務並添加中間件。這里使用的就是我們上方定義的配置類以及自定義用戶認證類

添加授權客戶端:AddInMemoryClients(Config.GetClients())
添加API資源:AddInMemoryApiResources(Config.GetApiResources())
添加身份資源:AddInMemoryIdentityResources(Config.GetIdentityResources())
添加自定義用戶認證:AddResourceOwnerValidator<UserPasswordValidator>();
        public void ConfigureServices(IServiceCollection services)
        {
            //注冊服務
            services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryClients(Config.GetClients())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddResourceOwnerValidator<UserPasswordValidator>();

            //添加數據庫配置
            services.AddDBContext(Configuration.GetValue<string>("ConnectionStrings:DB"));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //添加IdentityServer中間件
            app.UseIdentityServer();
        }

6.配置API端口:7000

  1)配置文件appsettings.json中增加端口配置節點。

{
  "Http": {
    "Port": 7000
  },
  "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }

  2)主程序Program.cs中添加端口監聽:

 1 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
 2             WebHost.CreateDefaultBuilder(args)
 3                 .ConfigureKestrel(options =>
 4                 {
 5                     //監聽端口
 6                     var config = options.ApplicationServices.GetRequiredService<IConfiguration>();
 7                     var port = config.GetValue<int>("Http:Port");
 8                     options.ListenAnyIP(port);
 9                 })
10                 .UseStartup<Startup>();

7.啟動項目

  

 第三步,搭建Ocelot網關服務

1.新建GateWay項目

  2.添加NuGet依賴包:Ocelot、Ocelot.Provider.Polly(服務質量與熔斷配置需引用Polly)、IdentityServer4.AccessTokenValidation

   

 

3.添加網關配置文件ocelot.json(配置文件名稱可自定義)。

    路由是API網關最基本也是最核心的功能、ReRoutes下就是由多個路由節點組成。

{
    "ReRoutes": [ ] }
注意:16.0版本開始之后,路由使用
Routes,否則會提示找不到路由。

幾個主要節點說明:
  • DownstreamPathTemplate:下游服務路徑(實際接口請求url)
  • DownstreamScheme:下游服務http schema
  • DownstreamHostAndPorts:下游服務的地址(包含Host:IP地址 、 Port:端口號),如果使用LoadBalancer(負載均衡)的話這里可以填多項
  • UpstreamPathTemplate: 上游服務路徑(客戶端輸入的請求Url)
  • UpstreamHttpMethod: 上游請求http方法,可使用數組,如:Get,Post等
  • AuthenticationOptions:添加此節點表示改路由需要進行權限認證
  • QosOptions:熔斷,配置什么情況下停止將請求轉發到下游服務。
 
{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 6000
        }
      ],
      "UpstreamPathTemplate": "/Service1/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "auth",
        "AllowedScopes": []
      },
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 5,
        "TimeoutValue": 10000
      }
    },
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 6002
        }
      ],
      "UpstreamPathTemplate": "/Service2/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "auth2",
        "AllowedScopes": []
      },
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 5,
        "TimeoutValue": 10000
      }
    },
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7000
        }
      ],
      "UpstreamPathTemplate": "/auth/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]

    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "",
    "RateLimitOptions": {
      "ClientWhitelist": [],
      "EnableRateLimiting": true,
      "Period": "1s",
      "PeriodTimespan": 1,
      "Limit": 1000
    }
  }
}

 4.添加啟用ocelot.json並配置端口號5000

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                   .ConfigureAppConfiguration((context, builder) =>
                    {   
//添加啟用配置文件
builder.SetBasePath(context.HostingEnvironment.ContentRootPath); builder.AddJsonFile(
"ocelot.json", optional: true, reloadOnChange: true); }) .UseKestrel(options => { //動態配置默認端口號5000 var config = options.ApplicationServices.GetRequiredService<IConfiguration>(); var httpPort = config["Http:Port"]; options.ListenAnyIP(Convert.ToInt32(httpPort)); }); UseStartup<Startup>();

5.注冊ocelot服務和Identity4認證

        public void ConfigureServices(IServiceCollection services)
        {
            //注冊ocelot服務 
            services.AddOcelot().AddPolly();
            //注冊Identity4認證
            services.AddAuthentication()
                .AddIdentityServerAuthentication("auth", option =>
                 {
                     option.Authority = "http://localhost:7000";
                     option.RequireHttpsMetadata = false;
                     option.ApiName = "api1";
                 })
                .AddIdentityServerAuthentication("auth2", option =>
                {
                    option.Authority = "http://localhost:7000";
                    option.RequireHttpsMetadata = false;
                    option.ApiName = "api2";
                });
            services.AddControllers();
        }

        
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //添加ocelot中間件
            app.UseOcelot().Wait();
        }

重點說明:

    1.這里我們需要注意AddIdentityServerAuthentication中參數值auth和auth2分別對應了ocelot配置文件中兩個API路由下鑒權節點AuthenticationOptions:AuthenticationProviderKey。

       這里綁定的對應關系,實際上也就是說第一個api啟用的auth對應的權限認證,並可以訪問api1資源;第二個api啟用auth2對應的權限認證,並可訪問api2資源。

    2.option.ApiName = "api1"這里指定可訪問api資源。此處配置的API資源來自我們在IdentityServer4服務中配置類中定義的API資源。

6.啟動項目

 

 

項目都搭建成功了,下面我們開始使用postman模擬請求,給大家演示一下效果。

1.首先請求token:IdentityServer4框架為我們開放了token獲取接口/connect/token

請求URL:http://locahost:5000/auth/connect/token   根據ocelot配置的路由上游模板auth/{url},此時會觸發下游服務:localhost:7000/connect/token  其實也就是我們搭建的IdentityServer4服務接口地址。

第一種方式,客戶端驗證模式。

  第二種方式,用戶名密碼模式

 2.請求第一個API項目接口/api/values

請求URL:http://locahost:5000/Service1/api/values   根據ocelot配置的路由上游模板Service1/{url},此時會觸發下游服務:localhost:6000/api/values  其實也就是我們搭建的第一個API服務接口地址。

此時,由於我們還沒有權限,提示401

 

 我們在請求頭加入client獲取的token,再次發起請求,請求成功。

 

 

 試想一下,如果我們用client2獲取的token,再次發起請求,會發生什么。。。可想而知,以失敗告終。那這是為什么呢?

舉個例子方便大家理解:

    當我們以client身份獲取token之后,訪問service1下面的接口,觸發Service1配置的auth認證,此認證允許訪問資源api1;而剛好IdentityServer4服務允許client訪問api1資源,請求成功;誠然,如果以client身份訪問Service2則會失敗,因為Service2配置的auth2認證,此認證允許訪問資源api2,而IdentityServer4服務僅允許client訪問api1資源。

2.請求第一個API項目接口/api/values

請求URL:http://locahost:5000/Service2/api/values   根據ocelot配置的路由上游模板Service2/{url},此時會觸發下游服務:localhost:6002/api/values  其實也就是我們搭建的第二個API服務接口地址。

此時,由於我們還沒有權限,提示401.

 

我們用client2獲取的token,再次發起請求,請求成功。

 

 

 想必到了這里,大家一定也有所想,有所言,歡迎大家交流。另外,大家不妨自己動手操作演示一下,或許更容易理解。


免責聲明!

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



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