溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何在ASP.NET Core3.1項(xiàng)目中實(shí)現(xiàn)一個(gè)Ocelot認(rèn)證功能

發(fā)布時(shí)間:2020-12-11 14:58:16 來(lái)源:億速云 閱讀:220 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

如何在ASP.NET Core3.1項(xiàng)目中實(shí)現(xiàn)一個(gè)Ocelot認(rèn)證功能?針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

1.認(rèn)證

當(dāng)客戶端通過(guò)Ocelot訪問(wèn)下游服務(wù)的時(shí)候,為了保護(hù)下游資源服務(wù)器會(huì)進(jìn)行認(rèn)證鑒權(quán),這時(shí)候需要在Ocelot添加認(rèn)證服務(wù)。添加認(rèn)證服務(wù)后,隨后Ocelot會(huì)基于授權(quán)密鑰授權(quán)每個(gè)請(qǐng)求可以訪問(wèn)的資源。用戶必須像往常一樣在其Startup.cs中注冊(cè)身份驗(yàn)證服務(wù),但是他們?yōu)槊看巫?cè)提供一個(gè)方案(身份驗(yàn)證提供者密鑰),例如:

public void ConfigureServices(IServiceCollection services)
{
  var authenticationProviderKey = "TestKey";
  services.AddAuthentication()
    .AddJwtBearer(authenticationProviderKey, x =>
    {
    });
}

在此Ocelot認(rèn)證項(xiàng)目示例中,TestKey是已注冊(cè)此提供程序的方案,然后將其映射到網(wǎng)關(guān)項(xiàng)目Routes路由中:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/customers",
      "DownstreamScheme": "http",
      "DownstreamHost": "localhost",
      "DownstreamPort": 9001,
      "UpstreamPathTemplate": "/customers",
      "UpstreamHttpMethod": [ "Get" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "TestKey",
        "AllowedScopes": []
      }
    }
  ]
}

Ocelot運(yùn)行時(shí),它將查看Routes.AuthenticationOptions.AuthenticationProviderKey并檢查是否存在使用給定密鑰注冊(cè)的身份驗(yàn)證提供程序。如果不存在,則Ocelot將不會(huì)啟動(dòng),如果存在,則Routes將在執(zhí)行時(shí)使用該提供程序。如果對(duì)路由進(jìn)行身份驗(yàn)證,Ocelot將在執(zhí)行身份驗(yàn)證中間件時(shí)調(diào)用與之關(guān)聯(lián)的任何方案。如果請(qǐng)求通過(guò)身份驗(yàn)證失敗,Ocelot將返回http狀態(tài)代碼401。

2.JWT Tokens Bearer認(rèn)證

Json Web Token (JWT),是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)放標(biāo)準(zhǔn)(RFC 7519)。該token被設(shè)計(jì)為緊湊且安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景。JWT的聲明一般被用來(lái)在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可被加密。

如何在ASP.NET Core3.1項(xiàng)目中實(shí)現(xiàn)一個(gè)Ocelot認(rèn)證功能

2.1JWT令牌結(jié)構(gòu)

在緊湊的形式中,JSON Web Tokens由dot(.)分隔的三個(gè)部分組成,它們是:Header頭、Payload有效載荷、Signature簽名。因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz(Header.Payload.Signature)。

2.1.1Header頭

標(biāo)頭通常由兩部分組成:令牌的類型,即JWT,以及正在使用的簽名算法,例如HMAC SHA256或RSA。例如:

{
 "alg": "HS256",
 "typ": "JWT"
}

然后,這個(gè)JSON被編碼為Base64Url,形成JWT的第一部分。

2.1.2Payload有效載荷

Payload部分也是一個(gè)JSON對(duì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT規(guī)定了7個(gè)官方字段,供選用。

  • iss (issuer):簽發(fā)人

  • exp (expiration time):過(guò)期時(shí)間

  • sub (subject):主題

  • aud (audience):受眾

  • nbf (Not Before):生效時(shí)間

  • iat (Issued At):簽發(fā)時(shí)間

  • jti (JWT ID):編號(hào)

除了官方字段,你還可以在這個(gè)部分定義私有字段,下面就是一個(gè)例子。例如:

{
 "sub": "1234567890",
 "name": "John Doe",
 "admin": true
}

注意,JWT默認(rèn)是不加密的,任何人都可以讀到,所以不要把秘密信息放在這個(gè)部分。這個(gè)JSON對(duì)象也要使用Base64URL算法轉(zhuǎn)成字符串。

2.1.3.Signature簽名

Signature部分是前兩部分的簽名,防止數(shù)據(jù)篡改。首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用Header里面指定的簽名算法(默認(rèn)是HMAC SHA256),按照下面的公式產(chǎn)生簽名:

HMACSHA256(
 base64UrlEncode(header) + "." +
 base64UrlEncode(payload),
 secret)

簽名用于驗(yàn)證消息在此過(guò)程中未被更改,并且,在使用私鑰簽名的令牌的情況下,它還可以驗(yàn)證JWT的發(fā)件人是否是它所聲稱的人。把他們?nèi)齻€(gè)全部放在一起,輸出是三個(gè)由點(diǎn)分隔的Base64-URL字符串,可以在HTML和HTTP環(huán)境中輕松傳遞,而與基于XML的標(biāo)準(zhǔn)(如SAML)相比更加緊湊。下面顯示了一個(gè)JWT,它具有先前的頭和有效負(fù)載編碼,并使用機(jī)密簽名:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3prNzAzIiwibmJmIjoiMTU5MjE0MzkzNyIsImV4cCI6MTU5MjE0Mzk5OCwiaXNzIjoiYXV0aC5qd3QuY2MiLCJhdWQiOiJkZW5nd3V8MjAyMC82LzE0IDIyOjEyOjE5In0
.4RiwhRy0rQkZjclOFWyTpmW7v0AMaL3aeve1L-eWIz0

其實(shí)一般發(fā)送用戶名和密碼獲取token那是由Identity4來(lái)完成的,包括驗(yàn)證用戶,生成JwtToken。但是項(xiàng)目這里是由System.IdentityModel.Tokens類庫(kù)來(lái)生成JwtToken。最后返回jwt令牌token給用戶。JwtToken解碼可以通過(guò)https://jwt.io/中進(jìn)行查看。

3.項(xiàng)目演示

3.1APIGateway項(xiàng)目

在該項(xiàng)目中啟用身份認(rèn)證來(lái)保護(hù)下游api服務(wù),使用JwtBearer認(rèn)證,將默認(rèn)的身份驗(yàn)證方案設(shè)置為TestKey。在appsettings.json文件中配置認(rèn)證中密鑰(Secret)跟受眾(Aud)信息:

{
  "Audience": {
    "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
    "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
    "Aud": "Catcher Wong"
  }
}

Startup添加身份認(rèn)證代碼如下:

public void ConfigureServices(IServiceCollection services)
{
  //獲取appsettings.json文件中配置認(rèn)證中密鑰(Secret)跟受眾(Aud)信息
  var audienceConfig = Configuration.GetSection("Audience");
  //獲取安全秘鑰
  var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
  //token要驗(yàn)證的參數(shù)集合
  var tokenValidationParameters = new TokenValidationParameters
  {
    //必須驗(yàn)證安全秘鑰
    ValidateIssuerSigningKey = true,
    //賦值安全秘鑰
    IssuerSigningKey = signingKey,
    //必須驗(yàn)證簽發(fā)人
    ValidateIssuer = true,
    //賦值簽發(fā)人
    ValidIssuer = audienceConfig["Iss"],
    //必須驗(yàn)證受眾
    ValidateAudience = true,
    //賦值受眾
    ValidAudience = audienceConfig["Aud"],
    //是否驗(yàn)證Token有效期,使用當(dāng)前時(shí)間與Token的Claims中的NotBefore和Expires對(duì)比
    ValidateLifetime = true,
    //允許的服務(wù)器時(shí)間偏移量
    ClockSkew = TimeSpan.Zero,
    //是否要求Token的Claims中必須包含Expires
    RequireExpirationTime = true,
  };
  //添加服務(wù)驗(yàn)證,方案為TestKey
  services.AddAuthentication(o =>
  {
    o.DefaultAuthenticateScheme = "TestKey";
  })
  .AddJwtBearer("TestKey", x =>
    {
      x.RequireHttpsMetadata = false;
      //在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發(fā)機(jī)構(gòu))、ValidAudience(頒發(fā)給誰(shuí))三個(gè)參數(shù)是必須的。
      x.TokenValidationParameters = tokenValidationParameters;
    });
  //添加Ocelot網(wǎng)關(guān)服務(wù)時(shí),包括Secret秘鑰、Iss簽發(fā)人、Aud受眾
  services.AddOcelot(Configuration);
}
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  //使用認(rèn)證服務(wù)
  app.UseAuthentication();
  //使用Ocelot中間件
  await app.UseOcelot();
}

3.1.1Identity Server承載JWT Token

在第二小節(jié)介紹JWT Token認(rèn)證時(shí)候,我們都知道一般發(fā)送用戶名和密碼獲取Token那是由Identity4來(lái)完成的,包括驗(yàn)證用戶,生成JWT Token。也就是說(shuō)Identity Server承載了JWT Token認(rèn)證功能。為了使用IdentityServer承載Token,請(qǐng)像往常一樣在ConfigureServices中使用方案(密鑰)注冊(cè)IdentityServer服務(wù)。如果您不知道如何執(zhí)行此操作,請(qǐng)查閱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參數(shù)指定OIDC服務(wù)地址,OIDC可以自動(dòng)發(fā)現(xiàn)Issuer, IssuerSigningKey等配置,而o.Audience與x.TokenValidationParameters = new TokenValidationParameters { ValidAudience = "api" }是等效的。

3.2AuthServer項(xiàng)目

此服務(wù)主要用于客戶端請(qǐng)求受保護(hù)的資源服務(wù)器時(shí),認(rèn)證后產(chǎn)生客戶端需要的JWT Token,生成JWT Token關(guān)鍵代碼如下:

[Route("api/[controller]")]
public class AuthController : Controller
{
  private IOptions<Audience> _settings;
  public AuthController(IOptions<Audience> settings)
  {
    this._settings = settings;
  }
  /// <summary>
  ///用戶使用 用戶名密碼 來(lái)請(qǐng)求服務(wù)器
  ///服務(wù)器進(jìn)行驗(yàn)證用戶的信息
  ///服務(wù)器通過(guò)驗(yàn)證發(fā)送給用戶一個(gè)token
  ///客戶端存儲(chǔ)token,并在每次請(qǐng)求時(shí)附送上這個(gè)token值, headers: {'Authorization': 'Bearer ' + token}
  ///服務(wù)端驗(yàn)證token值,并返回?cái)?shù)據(jù)
  /// </summary>
  /// <param name="name"></param>
  /// <param name="pwd"></param>
  /// <returns></returns>
  [HttpGet]
  public IActionResult Get(string name, string pwd)
  {
    //驗(yàn)證登錄用戶名和密碼
    if (name == "catcher" && pwd == "123")
    {
      var now = DateTime.UtcNow;
      //添加用戶的信息,轉(zhuǎn)成一組聲明,還可以寫入更多用戶信息聲明
      var claims = new Claim[]
      {
        //聲明主題
        new Claim(JwtRegisteredClaimNames.Sub, name),
          //JWT ID 唯一標(biāo)識(shí)符
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
          //發(fā)布時(shí)間戳 issued timestamp
        new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
      };
      //下面使用 Microsoft.IdentityModel.Tokens幫助庫(kù)下的類來(lái)創(chuàng)建JwtToken

      //安全秘鑰
      var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret));

      //聲明jwt驗(yàn)證參數(shù)
      var tokenValidationParameters = new TokenValidationParameters
      {
        //必須驗(yàn)證安全秘鑰
        ValidateIssuerSigningKey = true,
        //賦值安全秘鑰
        IssuerSigningKey = signingKey,
        //必須驗(yàn)證簽發(fā)人
        ValidateIssuer = true,
        //賦值簽發(fā)人
        ValidIssuer = _settings.Value.Iss,
        //必須驗(yàn)證受眾
        ValidateAudience = true,
        //賦值受眾
        ValidAudience = _settings.Value.Aud,
        //是否驗(yàn)證Token有效期,使用當(dāng)前時(shí)間與Token的Claims中的NotBefore和Expires對(duì)比
        ValidateLifetime = true,
        //允許的服務(wù)器時(shí)間偏移量
        ClockSkew = TimeSpan.Zero,
        //是否要求Token的Claims中必須包含Expires
        RequireExpirationTime = true,
      };
      var jwt = new JwtSecurityToken(
        //jwt簽發(fā)人
        issuer: _settings.Value.Iss,
        //jwt受眾
        audience: _settings.Value.Aud,
        //jwt一組聲明
        claims: claims,
        notBefore: now,
        //jwt令牌過(guò)期時(shí)間
        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文件中配置認(rèn)證中密鑰(Secret)跟受眾(Aud)信息:

{
  "Audience": {
    "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
    "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
    "Aud": "Catcher Wong"
  }
}

3.3CustomerAPIServices項(xiàng)目

該項(xiàng)目跟APIGateway項(xiàng)目是一樣的,為了保護(hù)下游api服務(wù),使用JwtBearer認(rèn)證,將默認(rèn)的身份驗(yàn)證方案設(shè)置為TestKey。在appsettings.json文件中配置認(rèn)證中密鑰(Secret)跟受眾(Aud)信息:

{
  "Audience": {
    "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
    "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
    "Aud": "Catcher Wong"
  }
}

Startup添加身份認(rèn)證代碼如下:

public void ConfigureServices(IServiceCollection services)
{
  //獲取appsettings.json文件中配置認(rèn)證中密鑰(Secret)跟受眾(Aud)信息
  var audienceConfig = Configuration.GetSection("Audience");
  //獲取安全秘鑰
  var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
  //token要驗(yàn)證的參數(shù)集合
  var tokenValidationParameters = new TokenValidationParameters
  {
    //必須驗(yàn)證安全秘鑰
    ValidateIssuerSigningKey = true,
    //賦值安全秘鑰
    IssuerSigningKey = signingKey,
    //必須驗(yàn)證簽發(fā)人
    ValidateIssuer = true,
    //賦值簽發(fā)人
    ValidIssuer = audienceConfig["Iss"],
    //必須驗(yàn)證受眾
    ValidateAudience = true,
    //賦值受眾
    ValidAudience = audienceConfig["Aud"],
    //是否驗(yàn)證Token有效期,使用當(dāng)前時(shí)間與Token的Claims中的NotBefore和Expires對(duì)比
    ValidateLifetime = true,
    //允許的服務(wù)器時(shí)間偏移量
    ClockSkew = TimeSpan.Zero,
    //是否要求Token的Claims中必須包含Expires
    RequireExpirationTime = true,
  };
  //添加服務(wù)驗(yàn)證,方案為TestKey
  services.AddAuthentication(o =>
  {
    o.DefaultAuthenticateScheme = "TestKey";
  })
  .AddJwtBearer("TestKey", x =>
    {
      x.RequireHttpsMetadata = false;
      //在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發(fā)機(jī)構(gòu))、ValidAudience(頒發(fā)給誰(shuí))三個(gè)參數(shù)是必須的。
      x.TokenValidationParameters = tokenValidationParameters;
    });

  services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
  //使用認(rèn)證服務(wù)
  app.UseAuthentication();
  app.UseMvc();
}

在CustomersController下添加一個(gè)需要認(rèn)證方法,一個(gè)不需要認(rèn)證方法:

[Route("api/[controller]")]
public class CustomersController : Controller
{
  //添加認(rèn)證屬性
  [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項(xiàng)目

該項(xiàng)目是用來(lái)模擬客戶端訪問(wèn)資源服務(wù)器整個(gè)認(rèn)證流程測(cè)試項(xiàng)目,在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;
  }
}

運(yùn)行項(xiàng)目看看測(cè)試結(jié)果:

如何在ASP.NET Core3.1項(xiàng)目中實(shí)現(xiàn)一個(gè)Ocelot認(rèn)證功能

結(jié)合代碼,我們能看到當(dāng)客戶端通過(guò)Ocelot網(wǎng)關(guān)訪問(wèn)下游服務(wù)http://localhost:9000/api/Customers/Get方法時(shí)候,因?yàn)樵摲椒ㄊ切枰ㄟ^(guò)認(rèn)證才返回處理結(jié)果的,所以會(huì)進(jìn)行JWT Token認(rèn)證,如果發(fā)現(xiàn)沒(méi)有Token,Ocelot則返回http狀態(tài)代碼401拒絕訪問(wèn)。如果我們通過(guò)GetJwt方法在AuthServer服務(wù)上登錄認(rèn)證獲取到授權(quán)Token,然后再訪問(wèn)該資源服務(wù)器接口,立即就會(huì)返回處理結(jié)果,通過(guò)跟而未加認(rèn)證屬性的http://localhost:9000/api/Customers/Get/{id}方法對(duì)比,我們就知道,Ocelot認(rèn)證已經(jīng)成功了!

關(guān)于如何在ASP.NET Core3.1項(xiàng)目中實(shí)現(xiàn)一個(gè)Ocelot認(rèn)證功能問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI