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