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,再次發起請求,請求成功。
想必到了這裡,大家一定也有所想,有所言,歡迎大家交流。另外,大家不妨自己動手操作演示一下,或許更容易理解。