簡介原文鏈接 .net core使用ocelot 第一篇 簡單使用 接上文,我將繼續介紹使用asp.net core 創建API網關,主要介紹身份驗證(authentication )相關內容。 API服務是需要保護的資源,我們應該儘可能的保證他們的安全。 通常,我們會使用aspnet securi ...
簡介原文鏈接
接上文,我將繼續介紹使用asp.net core 創建API網關,主要介紹身份驗證(authentication )相關內容。
API服務是需要保護的資源,我們應該儘可能的保證他們的安全。
通常,我們會使用aspnet security 來保證我們項目的安全,aspnet security代碼庫是為asp.net core 設計的安全和授權中間件。已經讓事情變得簡單。
那麼我們在項目中引入API網關會不會導致巨大的改變?答案是,不會。我們的修改微乎其微,但是卻讓安全和授權變得簡單。
先看下麵的截圖。
截圖顯示,當我們訪問我們的服務,API網關會讓我們首先訪問其授權模塊。我們必須訪問授權服務獲得訪問令牌(access token),然後用訪問令牌訪問受保護的服務。
可能你傾向將授權服務獨立,這意味客戶端得先訪問授權服務獲得訪問令牌。然後攜帶令牌訪問API網關。毫無疑問這樣做沒問題,但是我建議將授權服務和其他服務放在一起。
APIGateway是我們所有服務的入口,對於身份未驗證的客戶端僅可以訪問授權服務。
OK,開始我們的表演。
我會使用上一個demo的部分內容,便於理解。
Step1
項目名稱 |
項目類型 |
描述 |
APIGateway |
ASP.NET Core Empty |
示例的入口 |
CustomersAPIServices |
ASP.NET Core Web API |
API 服務處理消費者的操作 |
AuthServer |
ASP.NET Core Web API |
API 服務處理授權操作 |
ClientApp |
Console App |
控制台程式模擬客戶端 |
APIGateway和CustomerAPIServices和上篇文章的例子一樣,你可以在APIGatewayBasicDemo獲得。
Step2
創建AuthServer,AuthServer主要是為request請求生成訪問令牌(access token),我們需要添加一個方法處理。
[HttpGet] public IActionResult Get(string name, string pwd) { //just hard code here. if (name == "catcher" && pwd == "123") { var now = DateTime.UtcNow; var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, name), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64) }; var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret)); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = _settings.Value.Iss, ValidateAudience = true, ValidAudience = _settings.Value.Aud, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true, }; var jwt = new JwtSecurityToken( issuer: _settings.Value.Iss, audience: _settings.Value.Aud, claims: claims, notBefore: now, expires: now.Add(TimeSpan.FromMinutes(2)), signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256) ); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); var responseJson = new { access_token = encodedJwt, expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds }; return Json(responseJson); } else { return Json(""); } }
在驗證用戶時。我使用硬編碼將用戶名寫死,因為對於本文這個不是那麼重要。
這樣我們就完成了授權服務,現在跑起來。
Step3
回到CustomersAPIServices項目,我們應該保護這個服務。
修改Startup,我們可以使用授權。在這我用JwtBearer進行授權,我會給TestKey設置預設的授權方案。TestKey我將在下麵提到。
public void ConfigureServices(IServiceCollection services) { var audienceConfig = Configuration.GetSection("Audience"); var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"])); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Iss"], ValidateAudience = true, ValidAudience = audienceConfig["Aud"], ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true, }; services.AddAuthentication() .AddJwtBearer("TestKey", x => { x.RequireHttpsMetadata = false; x.TokenValidationParameters = tokenValidationParameters; }); services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseAuthentication(); app.UseMvc(); }
接下來,對需要保護的方法,添加Authorize。
[Authorize] [HttpGet] public IEnumerable<string> Get() { return new string[] { "Catcher Wong", "James Li" }; }
註意
該項目基於asp.net core 2.0. 如果你的項目是1.X,可能有些不同,建議用遷移遷移到2.0.以上。接下來就是見證奇跡的時候了。
Step4
最重要的步驟來了,在APIGateway中配置授權。
public void ConfigureServices(IServiceCollection services) { var audienceConfig = Configuration.GetSection("Audience"); var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"])); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Iss"], ValidateAudience = true, ValidAudience = audienceConfig["Aud"], ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true, }; services.AddAuthentication() .AddJwtBearer("TestKey", x => { x.RequireHttpsMetadata = false; x.TokenValidationParameters = tokenValidationParameters; }); services.AddOcelot(Configuration); }
這個配置的大部分代碼和Customer Service一樣。
當Ocelot啟動,它會查看ReRoutes 》AuthenticationOptions 》AuthenticationProviderKey 的值,
檢查該值是否被授權服務註冊,如果沒有,Ocelot不會啟動,如果有,Ocelot在執行時使用授權服務。
所以,我們得修改configuration.json,我們需要添加新的節點,並將其值賦為在Startup 類中定義的一樣。
{ "DownstreamPathTemplate": "/api/customers", "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 9001, "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": [ "Get" ], "AuthenticationOptions": { "AuthenticationProviderKey": "TestKey", "AllowedScopes": [] } }
啟動服務。
註意
當你啟動項目時遇到下麵的錯誤,你應該檢查你的代碼,查看AddJwtBearer 方法是否指明授權方案。
Unhandled Exception: System.InvalidOperationException: Scheme already exists: Bearer
Step5
我們已經準備完畢,我們用我們的客戶端模擬APIGateway的某些請求。
我們先添加獲得訪問令牌的方法。
private static string GetJwt() { HttpClient client = new HttpClient(); client.BaseAddress = new Uri( "http://localhost:9000"); client.DefaultRequestHeaders.Clear(); var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result; dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result); return jwt.access_token; }
接下來,編寫通過APIGateway訪問Customer Service方法的代碼。
static void Main(string[] args) { HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Clear(); client.BaseAddress = new Uri("http://localhost:9000"); // 1. without access_token will not access the service // and return 401 . var resWithoutToken = client.GetAsync("/customers").Result; //print something here //2. with access_token will access the service // and return result. client.DefaultRequestHeaders.Clear(); var jwt = GetJwt(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}"); var resWithToken = client.GetAsync("/customers").Result; //print something here //3. visit no auth service client.DefaultRequestHeaders.Clear(); var res = client.GetAsync("/customers/1").Result; //print something here Console.Read(); }
運行結果。
完工。
源碼在此。
總結
沒啥。