1.認證
當客戶端通過Ocelot訪問下游服務的時候,為了保護下游資源服務器會進行認證鑒權,這時候需要在Ocelot添加認證服務,添加認證服務后,隨后使用Ocelot基于宣告的任何功能,例如授權或使用Token中的值修改請求,用戶必須像往常一樣在其Startup.cs中注冊身份驗證服務,但是他們為每次注冊提供一個方案(身份驗證提供者密鑰),例如:
public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; services.AddAuthentication() .AddJwtBearer(authenticationProviderKey, x => { }); }
在此Ocelot認證專案示例中,TestKey是已注冊此提供程式的方案,然后我們將其映射到配置中的Routes路由,例如:
{ "Routes": [ { "DownstreamPathTemplate": "/api/customers", "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 9001, "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": [ "Get" ], "AuthenticationOptions": { "AuthenticationProviderKey": "TestKey", "AllowedScopes": [] } } ] }
Ocelot運行時,它將查看Routes.AuthenticationOptions.AuthenticationProviderKey并檢查是否存在使用給定密鑰注冊的身份驗證提供程式,如果不存在,則Ocelot將不會啟動,如果存在,則Routes將在執行時使用該提供程式,如果對路由進行身份驗證,Ocelot將在執行身份驗證中間件時呼叫與之關聯的任何方案,如果請求通過身份驗證失敗,Ocelot將回傳http狀態代碼401,
2.JWT Tokens Bearer認證
Json Web Token (JWT),是為了在網路應用環境間傳遞宣告而執行的一種基于JSON的開放標準(RFC 7519),該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景,JWT的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份資訊,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該token也可直接被用于認證,也可被加密,
2.1JWT令牌結構
在緊湊的形式中,JSON Web Tokens由dot(.)分隔的三個部分組成,它們是:Header頭、Payload有效載荷、Signature簽名,因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz(Header.Payload.Signature),
2.1.1Header頭
標頭通常由兩部分組成:令牌的型別,即JWT,以及正在使用的簽名演算法,例如HMAC SHA256或RSA,例如:
{ "alg": "HS256", "typ": "JWT" }
然后,這個JSON被編碼為Base64Url,形成JWT的第一部分,
2.1.2Payload有效載荷
Payload部分也是一個JSON物件,用來存放實際需要傳遞的資料,JWT規定了7個官方欄位,供選用,
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
除了官方欄位,你還可以在這個部分定義私有欄位,下面就是一個例子,例如:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
注意,JWT默認是不加密的,任何人都可以讀到,所以不要把秘密資訊放在這個部分,這個JSON物件也要使用Base64URL演算法轉成字串,
2.1.3.Signature簽名
Signature部分是前兩部分的簽名,防止資料篡改,首先,需要指定一個密鑰(secret),這個密鑰只有服務器才知道,不能泄露給用戶,然后,使用Header里面指定的簽名演算法(默認是HMAC SHA256),按照下面的公式產生簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名用于驗證訊息在此程序中未被更改,并且,在使用私鑰簽名的令牌的情況下,它還可以驗證JWT的發件人是否是它所聲稱的人,把他們三個全部放在一起,輸出是三個由點分隔的Base64-URL字串,可以在HTML和HTTP環境中輕松傳遞,而與基于XML的標準(如SAML)相比更加緊湊,下面顯示了一個JWT,它具有先前的頭和有效負載編碼,并使用機密簽名:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3prNzAzIiwibmJmIjoiMTU5MjE0MzkzNyIsImV4cCI6MTU5MjE0Mzk5OCwiaXNzIjoiYXV0aC5qd3QuY2MiLCJhdWQiOiJkZW5nd3V8MjAyMC82LzE0IDIyOjEyOjE5In0
.4RiwhRy0rQkZjclOFWyTpmW7v0AMaL3aeve1L-eWIz0
其實一般發送用戶名和密碼獲取token那是由Identity4來完成的,包括驗證用戶,生成JwtToken,但是專案這里是由System.IdentityModel.Tokens類別庫來生成JwtToken,最后回傳jwt令牌token給用戶,JwtToken解碼可以通過https://jwt.io/中進行查看,
3.專案演示
3.1APIGateway專案
在該專案中啟用身份認證來保護下游api服務,使用JwtBearer認證,將默認的身份驗證方案設定為TestKey,在appsettings.json檔案中配置認證中密鑰(Secret)跟受眾(Aud)資訊:
{ "Audience": { "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==", "Iss": "http://www.c-sharpcorner.com/members/catcher-wong", "Aud": "Catcher Wong" } }
Startup添加身份認證代碼如下:
public void ConfigureServices(IServiceCollection services) { //獲取appsettings.json檔案中配置認證中密鑰(Secret)跟受眾(Aud)資訊 var audienceConfig = Configuration.GetSection("Audience"); //獲取安全秘鑰 var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"])); //token要驗證的引數集合 var tokenValidationParameters = new TokenValidationParameters { //必須驗證安全秘鑰 ValidateIssuerSigningKey = true, //賦值安全秘鑰 IssuerSigningKey = signingKey, //必須驗證簽發人 ValidateIssuer = true, //賦值簽發人 ValidIssuer = audienceConfig["Iss"], //必須驗證受眾 ValidateAudience = true, //賦值受眾 ValidAudience = audienceConfig["Aud"], //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比 ValidateLifetime = true, //允許的服務器時間偏移量 ClockSkew = TimeSpan.Zero, //是否要求Token的Claims中必須包含Expires RequireExpirationTime = true, }; //添加服務驗證,方案為TestKey services.AddAuthentication(o => { o.DefaultAuthenticateScheme = "TestKey"; }) .AddJwtBearer("TestKey", x => { x.RequireHttpsMetadata = false; //在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發機構)、ValidAudience(頒發給誰)三個引數是必須的, x.TokenValidationParameters = tokenValidationParameters; }); //添加Ocelot網關服務時,包括Secret秘鑰、Iss簽發人、Aud受眾 services.AddOcelot(Configuration); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { //使用認證服務 app.UseAuthentication(); //使用Ocelot中間件 await app.UseOcelot(); }
3.1.1Identity Server承載JWT Token
在第二小節介紹JWT Token認證時候,我們都知道一般發送用戶名和密碼獲取Token那是由Identity4來完成的,包括驗證用戶,生成JWT Token,也就是說Identity Server承載了JWT Token認證功能,為了使用IdentityServer承載Token,請像往常一樣在ConfigureServices中使用方案(密鑰)注冊IdentityServer服務,如果您不知道如何執行此操作,請查閱IdentityServer檔案,
public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; Action<IdentityServerAuthenticationOptions> options = o => { o.Authority = "https://whereyouridentityserverlives.com"; o.ApiName = "api"; o.SupportedTokens = SupportedTokens.Both; o.ApiSecret = "secret"; }; services.AddAuthentication() .AddIdentityServerAuthentication(authenticationProviderKey, options); services.AddOcelot(); }
在Identity4中是由Authority引數指定OIDC服務地址,OIDC可以自動發現Issuer, IssuerSigningKey等配置,而o.Audience與x.TokenValidationParameters = new TokenValidationParameters { ValidAudience = "api" }是等效的,
3.2AuthServer專案
此服務主要用于客戶端請求受保護的資源服務器時,認證后產生客戶端需要的JWT Token,生成JWT Token關鍵代碼如下:
[Route("api/[controller]")] public class AuthController : Controller { private IOptions<Audience> _settings; public AuthController(IOptions<Audience> settings) { this._settings = settings; } /// <summary> ///用戶使用 用戶名密碼 來請求服務器 ///服務器進行驗證用戶的資訊 ///服務器通過驗證發送給用戶一個token ///客戶端存盤token,并在每次請求時附送上這個token值, headers: {'Authorization': 'Bearer ' + token} ///服務端驗證token值,并回傳資料 /// </summary> /// <param name="name"></param> /// <param name="pwd"></param> /// <returns></returns> [HttpGet] public IActionResult Get(string name, string pwd) { //驗證登錄用戶名和密碼 if (name == "catcher" && pwd == "123") { var now = DateTime.UtcNow; //添加用戶的資訊,轉成一組宣告,還可以寫入更多用戶資訊宣告 var claims = new Claim[] { //宣告主題 new Claim(JwtRegisteredClaimNames.Sub, name), //JWT ID 唯一識別符號 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), //發布時間戳 issued timestamp new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64) }; //下面使用 Microsoft.IdentityModel.Tokens幫助庫下的類來創建JwtToken //安全秘鑰 var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret)); //宣告jwt驗證引數 var tokenValidationParameters = new TokenValidationParameters { //必須驗證安全秘鑰 ValidateIssuerSigningKey = true, //賦值安全秘鑰 IssuerSigningKey = signingKey, //必須驗證簽發人 ValidateIssuer = true, //賦值簽發人 ValidIssuer = _settings.Value.Iss, //必須驗證受眾 ValidateAudience = true, //賦值受眾 ValidAudience = _settings.Value.Aud, //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比 ValidateLifetime = true, //允許的服務器時間偏移量 ClockSkew = TimeSpan.Zero, //是否要求Token的Claims中必須包含Expires RequireExpirationTime = true, }; var jwt = new JwtSecurityToken( //jwt簽發人 issuer: _settings.Value.Iss, //jwt受眾 audience: _settings.Value.Aud, //jwt一組宣告 claims: claims, notBefore: now, //jwt令牌過期時間 expires: now.Add(TimeSpan.FromMinutes(2)), //簽名憑證: 安全密鑰、簽名演算法 signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256) ); //生成jwt令牌(json web token) 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(""); } } } public class Audience { public string Secret { get; set; } public string Iss { get; set; } public string Aud { get; set; } }
appsettings.json檔案中配置認證中密鑰(Secret)跟受眾(Aud)資訊:
{ "Audience": { "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==", "Iss": "http://www.c-sharpcorner.com/members/catcher-wong", "Aud": "Catcher Wong" } }
3.3CustomerAPIServices專案
該專案跟APIGateway專案是一樣的,為了保護下游api服務,使用JwtBearer認證,將默認的身份驗證方案設定為TestKey,在appsettings.json檔案中配置認證中密鑰(Secret)跟受眾(Aud)資訊:
{ "Audience": { "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==", "Iss": "http://www.c-sharpcorner.com/members/catcher-wong", "Aud": "Catcher Wong" } }
Startup添加身份認證代碼如下:
public void ConfigureServices(IServiceCollection services) { //獲取appsettings.json檔案中配置認證中密鑰(Secret)跟受眾(Aud)資訊 var audienceConfig = Configuration.GetSection("Audience"); //獲取安全秘鑰 var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"])); //token要驗證的引數集合 var tokenValidationParameters = new TokenValidationParameters { //必須驗證安全秘鑰 ValidateIssuerSigningKey = true, //賦值安全秘鑰 IssuerSigningKey = signingKey, //必須驗證簽發人 ValidateIssuer = true, //賦值簽發人 ValidIssuer = audienceConfig["Iss"], //必須驗證受眾 ValidateAudience = true, //賦值受眾 ValidAudience = audienceConfig["Aud"], //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比 ValidateLifetime = true, //允許的服務器時間偏移量 ClockSkew = TimeSpan.Zero, //是否要求Token的Claims中必須包含Expires RequireExpirationTime = true, }; //添加服務驗證,方案為TestKey services.AddAuthentication(o => { o.DefaultAuthenticateScheme = "TestKey"; }) .AddJwtBearer("TestKey", x => { x.RequireHttpsMetadata = false; //在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發機構)、ValidAudience(頒發給誰)三個引數是必須的, x.TokenValidationParameters = tokenValidationParameters; }); services.AddMvc(); } public void Configure(IApplicationBuilder app) { //使用認證服務 app.UseAuthentication(); app.UseMvc(); }
在CustomersController下添加一個需要認證方法,一個不需要認證方法:
[Route("api/[controller]")] public class CustomersController : Controller { //添加認證屬性 [Authorize] [HttpGet] public IEnumerable<string> Get() { return new string[] { "Catcher Wong", "James Li" }; } [HttpGet("{id}")] public string Get(int id) { return $"Catcher Wong - {id}"; } }
3.4ClientApp專案
該專案是用來模擬客戶端訪問資源服務器整個認證流程測驗專案,在Program主程式可以看到如下代碼:
class Program { 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; Console.WriteLine($"Sending Request to /customers , without token."); Console.WriteLine($"Result : {resWithoutToken.StatusCode}"); //2. with access_token will access the service // and return result. client.DefaultRequestHeaders.Clear(); Console.WriteLine("\nBegin Auth...."); var jwt = GetJwt(); Console.WriteLine("End Auth...."); Console.WriteLine($"\nToken={jwt}"); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}"); var resWithToken = client.GetAsync("/customers").Result; Console.WriteLine($"\nSend Request to /customers , with token."); Console.WriteLine($"Result : {resWithToken.StatusCode}"); Console.WriteLine(resWithToken.Content.ReadAsStringAsync().Result); //3. visit no auth service Console.WriteLine("\nNo Auth Service Here "); client.DefaultRequestHeaders.Clear(); var res = client.GetAsync("/customers/1").Result; Console.WriteLine($"Send Request to /customers/1"); Console.WriteLine($"Result : {res.StatusCode}"); Console.WriteLine(res.Content.ReadAsStringAsync().Result); Console.Read(); } 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; } }
運行專案看看測驗結果:
結合代碼,我們能看到當客戶端通過Ocelot網關訪問下游服務http://localhost:9000/api/Customers/Get方法時候,因為該方法是需要通過認證才回傳處理結果的,所以會進行JWT Token認證,如果發現沒有Token,Ocelot則回傳http狀態代碼401拒絕訪問,如果我們通過GetJwt方法在AuthServer服務上登錄認證獲取到授權Token,然后再訪問該資源服務器介面,立即就會回傳處理結果,通過跟而未加認證屬性的http://localhost:9000/api/Customers/Get/{id}方法對比,我們就知道,Ocelot認證已經成功了!
4.總結
該章節只是結合demo專案簡單介紹在Ocelot中如何使用JWT Token認證,其實正式環境中,Ocelot是應該集成IdentityServer認證授權的,同樣的通過重寫Ocelot中間件我們還可以把configuration.json的配置資訊存盤到資料庫或者快取到Redis中,
參考文獻:
Ocelot官網
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/208247.html
標籤:.NET技术
上一篇:(3)ASP.NET Core3.1 Ocelot認證
下一篇:說說 C# 9 新特性的實際運用
