.Net Core官方的 JWT 受權驗證

什麼是JWT?

JSON Web令牌(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間安全地傳輸信息做爲JSON對象。因爲此信息是通過數字簽名的,所以能夠被驗證和信任。可使用祕密(使用HMAC算法)或使用RSAECDSA的公鑰/私鑰對對JWT進行簽名html

儘管能夠對JWT進行加密以提供雙方之間的保密性,但咱們將重點關注已簽名的令牌。簽名的令牌能夠驗證其中包含的聲明完整性,而加密的令牌則將這些聲明隱藏在其餘方的面前。當使用公鑰/私鑰對對令牌進行簽名時,簽名還證實只有持有私鑰的一方纔是對其進行簽名的一方。因爲缺少安全性,因此不能把如密碼等敏感信息放在令牌中。web

何時應該使用JWT?

如下是JSON Web令牌有用的一些狀況:算法

  • 受權:這是使用JWT的最多見方案。一旦用戶登陸,每一個後續請求將包括JWT,從而容許用戶訪問該令牌容許的路由,服務和資源。單一登陸是當今普遍使用JWT的一項功能,由於它的開銷很小而且能夠在不一樣的域中輕鬆使用。數據庫

  • 信息交換:JSON Web令牌是在各方之間安全傳輸信息的好方法。由於能夠對JWT進行簽名(例如,使用公鑰/私鑰對),因此您能夠肯定發件人是他們所說的人。此外,因爲簽名是使用標頭和有效負載計算的,所以您還能夠驗證內容是否未被篡改。json

JWT結構是什麼?

JSON Web令牌以緊湊的形式由三部分組成,這些部分由點(.分隔,分別是:api

  • 標頭
  • 有效載荷
  • 簽名

所以,JWT一般以下所示。跨域

xxxxx.yyyyy.zzzzz安全

標頭:一般由兩部分組成,令牌類型和使用的簽名算法。服務器

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

有效載荷:有三種說明類型,預約義聲明、公共聲明和私有聲明。聲明名稱僅是三個字符,由於JWT是緊湊的cookie

  • 預約義聲明:包括iss(發出者), exp(到期時間), sub(主題), aud(受衆)等,是推薦的可是不是強制的能夠沒有。
  • 公共聲明:公共聲明,這個部分能夠隨便定義,可是要注意和 IANA JSON Web Token 衝突。
  • 私有聲明:這個部分是共享被認定信息中自定義部分。

簽名要建立簽名部分,您必須獲取編碼的標頭,編碼的有效負載,機密,標頭中指定的算法,並對其進行簽名。

例如,若是要使用HMAC SHA256算法,則將經過如下方式建立簽名:

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

簽名用於驗證消息在此過程當中沒有更改,而且對於使用私鑰進行簽名的令牌,它還能夠驗證JWT的發送者是它所說的真實身份。

組合在一塊兒以下爲輸出是三個由點分隔的Base64-URL字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUyMDAiLCJhdWQiOlsiYXBpIiwiYXBpIl0sIkd1aWQiOiIzM2NhZmJkNS1jZWEyLTRjOWMtYWZlYS01MDYyZjM3YWUyOTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsic3lzdGVtIiwiYWRtaW4iXSwiZXhwIjoxNjA2NjU2NjI0fQ.
JvDHuowbOnWiyxMIFc9gG5rw1LSSc0xx68L31oRfxS0

如何使用JWT

每當用戶想要訪問受保護的路由或資源時,用戶代理都應發送JWT,一般使用承載模式Authorization標頭中發送JWT 標頭的內容應以下所示:

Authorization: Bearer <token>

在某些狀況下,這能夠是無狀態受權機制。服務器的受保護路由將在Authorization標頭中檢查有效的JWT ,若是存在,則將容許用戶訪問受保護的資源。若是JWT包含必要的數據,則能夠減小查詢數據庫中某些操做的需求,儘管這種狀況並不是老是如此。

若是令牌是在Authorization標頭中發送的,則跨域資源共享(CORS)不會成爲問題,由於它不使用cookie。

下圖顯示瞭如何獲取JWT並將其用於訪問API或資源:

JSON Web令牌如何工做

  1. 應用程序或客戶端向受權服務器請求受權。這是經過不一樣的受權流程之一執行的。例如,典型的符合OpenID Connect的Web應用程序將/oauth/authorize使用受權代碼流經過端點
  2. 授予受權後,受權服務器會將訪問令牌返回給應用程序。
  3. 該應用程序使用訪問令牌來訪問受保護的資源(例如API)。

請注意,使用簽名的令牌,令牌中包含的全部信息都會暴露給用戶或其餘方,即便他們沒法更改它。這意味着您不該將機密信息放入令牌中。

.net core的JWT驗證受權

咱們直接新建一個.net core webapi的項目,我這裏版本是3.1的

1. 先使用nuget安裝:Microsoft.AspNetCore.Authentication.JwtBearer。注意版本和.net core版本的兼容。net5的支持5.0.0+的版本,不然就用對應能夠用的低版本吧。

2. 在appsettings.json配置文件中寫好咱們的 JWT 的配置參數以下:

  "Jwt": {
    "Secret": "your-256-bit-secret",
    "Iss": "https://localhost:44355",
    "Aud": "api"
  }

3. 在Startup.cs的ConfigureServices方法中添加受權認證以下:

var jwtConfig = Configuration.GetSection("Jwt");
//生成密鑰
var symmetricKeyAsBase64 = jwtConfig.GetValue<string>("Secret");
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
//認證參數
services.AddAuthentication("Bearer")
    .AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,//是否驗證簽名,不驗證的畫能夠篡改數據,不安全
            IssuerSigningKey = signingKey,//解密的密鑰
            ValidateIssuer = true,//是否驗證發行人,就是驗證載荷中的Iss是否對應ValidIssuer參數
            ValidIssuer = jwtConfig.GetValue<string>("Iss"),//發行人
            ValidateAudience = true,//是否驗證訂閱人,就是驗證載荷中的Aud是否對應ValidAudience參數
            ValidAudience = jwtConfig.GetValue<string>("Aud"),//訂閱人
            ValidateLifetime = true,//是否驗證過時時間,過時了就拒絕訪問
            ClockSkew = TimeSpan.Zero,//這個是緩衝過時時間,也就是說,即便咱們配置了過時時間,這裏也要考慮進去,過時時間+緩衝,默認好像是7分鐘,你能夠直接設置爲0
            RequireExpirationTime = true,
        };
    });

在Configure方法中添加 app.UseAuthentication() 和 app.UseAuthorization() 注意位置須要放置的位置:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>{ endpoints.MapControllers();});
}

4. 生成jwt令牌,在默認生成的控制器 WeatherForecastController 中添加以下生成令牌的方法:

[HttpPost]
public IActionResult Authenticate()
{
    var jwtConfig = Configuration.GetSection("Jwt");
    //祕鑰,就是標頭,這裏用Hmacsha256算法,須要256bit的密鑰
    var securityKey = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.GetValue<string>("Secret"))), SecurityAlgorithms.HmacSha256);
    //Claim,JwtRegisteredClaimNames中預約義了好多種默認的參數名,也能夠像下面的Guid同樣本身定義鍵名.
    //ClaimTypes也預約義了好多類型如role、email、name。Role用於賦予權限,不一樣的角色能夠訪問不一樣的接口
    //至關於有效載荷
    var claims = new Claim[] {
                new Claim(JwtRegisteredClaimNames.Iss,jwtConfig.GetValue<string>("Iss")),
                new Claim(JwtRegisteredClaimNames.Aud,jwtConfig.GetValue<string>("Aud")),
                new Claim("Guid",Guid.NewGuid().ToString("D")),
                new Claim(ClaimTypes.Role,"system"),
                new Claim(ClaimTypes.Role,"admin"),
    };
    SecurityToken securityToken = new JwtSecurityToken(
        signingCredentials: securityKey,
        expires: DateTime.Now.AddMinutes(2),//過時時間
        claims: claims
    );
    //生成jwt令牌
    return Content(new JwtSecurityTokenHandler().WriteToken(securityToken));
}

5. 使用jwt控制接口的訪問,咱們在一個接口上添加一個特性 [Authorize(Roles ="admin")],表示須要有admin這個角色的jwt令牌才能訪問,沒有roles參數的話表示只要是可用的令牌就能夠訪問,多個role角色能夠疊加多個特性:

[HttpGet]
[Authorize(Roles = "admin")]
[Authorize(Roles = "system")]
public IEnumerable<WeatherForecast> Get()
{
    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
}

6.測試,而後咱們用postman就能夠測試了,能夠看到很是成功有數據。

 

 

 進階

認證的時候能夠添加事件,以下面的認證失敗事件、接收參數事件能夠獲取url上的參數做爲令牌而不是經過http的請求頭的Authorization

services.AddAuthentication("Bearer")
    .AddJwtBearer(o =>
    {
        o.Events = new JwtBearerEvents()
        {
            OnMessageReceived = context =>
            {
                context.Token = context.Request.Query["access_token"];
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                // 若是過時,則把<是否過時>添加到,返回頭信息中
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });
相關文章
相關標籤/搜索