.NET Core JWT 認(rèn)證

JWT 介紹

JWT(JSON Web Token)是一種開放標(biāo)準(zhǔn),它以 JSON 對象的方式在各方之間安全地傳輸信息训枢。通俗的說忘巧,就是通過數(shù)字簽名算法生產(chǎn)一個字符串,然后在網(wǎng)絡(luò)請求的中被攜帶到服務(wù)端進(jìn)行身份認(rèn)證十酣,功能上來說和 SessionId 認(rèn)證方式很像际长。

JWT 與 SessionId 認(rèn)證對比

SessionId 認(rèn)證方式一般做法是用戶登錄成功后工育,服務(wù)端生成一個 SessionId,然后將 SessionId 和 用戶的關(guān)系進(jìn)行存儲(內(nèi)存如绸、Redis怔接、數(shù)據(jù)庫等),之后將 SessionId 寫入 Cookie(一般是主域名下森书,方便單點登錄) 或返回給調(diào)用方谎势,后續(xù)的所有請求都攜帶這個 SessionId 到服務(wù)端進(jìn)行身份認(rèn)證。

SessionId

而 JWT 最大區(qū)別是登錄狀態(tài)不在服務(wù)端進(jìn)行存儲猖毫,而是通過密鑰生成一個具有有效時間的 Token 返回給前端须喂,Token 中包含類似用戶 Id 等信息趁蕊,且是不允許被篡改的掷伙,之后的請求將 Token 攜帶到服務(wù)端進(jìn)行認(rèn)證又兵,認(rèn)證通過后可解析 Token 拿到用戶標(biāo)識進(jìn)行后續(xù)操作。

JWT

JWT 構(gòu)成

Header

header 典型的由兩部分組成:token的類型(JWT)和算法名稱(HMAC宙地、SHA256逆皮、RSA等)

如:

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

通過 Base64 對這個 JSON 編碼就得到 JWT 的第一部分电谣。

Payload

它包含關(guān)于實體(通常是用戶)和其他數(shù)據(jù)的聲明,分別是 Registered辰企、Public 和 Private 三種類型牢贸。

  • Registered claims : 預(yù)定義的聲明,它們不是強(qiáng)制的潜索,但是推薦竹习。如:iss (issuer)、exp (expiration time)拗窃、sub (subject)泌辫、aud (audience) 等
  • Public claims : 可隨意定義
  • Private claims : 用于在同意使用它們的各方之間共享信息

如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

通過 Base64 對這個 JSON 編碼就得到 JWT 的第二部分。

Signature

Signature用來驗證發(fā)送請求者身份宾毒,由前兩部分加密形成殿遂。

如:

var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

通過 Header+Payload+Signature 就得到如下結(jié)果:

Token

注意:Payload 最終是可以被解析成明文的,所以在設(shè)置 Payload 時一定不能將非加密的敏感信息存儲在內(nèi)

JWT 使用方式

JWT Access API
  1. 客戶端到認(rèn)證服務(wù)進(jìn)行認(rèn)證
  2. 認(rèn)證成功返回 Token
  3. 客戶端在請求頭中加入 Authorization: Bearer {Token} 訪問 API 資源

.NET Core 集成 JWT

搭建 JWTServer

  1. 創(chuàng)建 JWTServer(.NET Core Web API)項目 (用來進(jìn)行身份認(rèn)證及生成 Token)耳峦;

  2. Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer

  3. 配置文件中加入 JWT 相關(guān)參數(shù)

    "JwtSetting": {
      "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰
      "Issuer": "jwtIssuertest",        // 頒發(fā)者
      "Audience": "jwtAudiencetest",    // 接收者
      "ExpireSeconds": 20           // 過期時間(20s)
    }
    
  4. 添加用戶登錄接口,模擬身份認(rèn)證

    private readonly static User User = new User
    {
      Id = 1,
      Name = "beck",
      Password = "123456"
    };
    
    public async Task<User> LoginAsync(string name, string password)
    {
      await Task.CompletedTask;
      if (User.Name == name && User.Password == password)
      {
        return User;
      }
      return null;
    }
    
  5. 用戶認(rèn)證成功后獲得 User 詳細(xì)信息咬荷,然后生成 Token

    public string GetToken(User user)
    {
      //創(chuàng)建用戶身份標(biāo)識轻掩,可按需要添加更多信息
      var claims = new Claim[]
      {
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim("id", user.Id.ToString(), ClaimValueTypes.Integer32), // 用戶id
        new Claim("name", user.Name), // 用戶名
        new Claim("admin", user.IsAdmin.ToString(),ClaimValueTypes.Boolean) // 是否是管理員
      };
    
      //創(chuàng)建令牌
      var token = new JwtSecurityToken(
        issuer: _jwtSetting.Issuer,
        audience: _jwtSetting.Audience,
        signingCredentials: _jwtSetting.Credentials,
        claims: claims,
        notBefore: DateTime.Now,
        expires: DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds)
      );
    
      string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
    
      return jwtToken;
    }
    
  6. 返回 Token 信息

    {
      "Status": true,
      "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3Njg1MDUwMS1kZTk5LTRmYmYtYmVlNy01ODAxMjY5ZjNiMTgiLCJpZCI6MSwibmFtZSI6ImJlY2siLCJhZG1pbiI6dHJ1ZSwibmJmIjoxNTUzMzU2MTc1LCJleHAiOjE1NTMzNzYxNzUsImlzcyI6Imp3dElzc3VlcnRlc3QiLCJhdWQiOiJqd3RBdWRpZW5jZXRlc3QifQ.O15rMLMHADGkmhsCNAhcrCMO6c5iQzkXHfbU0jj5HaM",
      "Type": "Bearer"
    }
    
  7. 將 Token 在 https://jwt.io/ 進(jìn)行解析查看效果:

    Token Decoded

搭建 TestApi

  1. 創(chuàng)建 TestApi(.NET Core Web API)項目 (模擬需要身份認(rèn)證的 API 接口)

  2. Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer

  3. 配置文件中加入 JWT 相關(guān)參數(shù)

    "JwtSetting": {
      "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰
      "Issuer": "jwtIssuertest",        // 頒發(fā)者
      "Audience": "jwtAudiencetest"     // 接收者
    }
    
  4. Startup 的 ConfigureServices 方法加入如下代碼:

    var jwtSetting = new JwtSetting();
    Configuration.Bind("JwtSetting", jwtSetting);
    
    services
      .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
      .AddJwtBearer(options =>
      {
        options.TokenValidationParameters = new TokenValidationParameters
        {
          ValidIssuer = jwtSetting.Issuer,
          ValidAudience = jwtSetting.Audience,
          IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
          // 默認(rèn)允許 300s  的時間偏移量罕扎,設(shè)置為0
          ClockSkew = TimeSpan.Zero
        };
      });
    
  5. Startup 的 Configure 方法加入如下代碼:

    app.UseAuthentication();
    
  6. 在需要認(rèn)證的接口上加 [Authorize] 特性

    [HttpGet]
    [Authorize]
    public async Task<string> Get()
    {
      await Task.CompletedTask;
    
      return $"{_identityService.GetUserId()}:{_identityService.GetUserName()}";
    }
    
    public class IdentityService : IIdentityService
    {
      private readonly IHttpContextAccessor _context;
    
      public IdentityService(IHttpContextAccessor context)
      {
        _context = context;
      }
      
      public int GetUserId()
      {
        var nameId = _context.HttpContext.User.FindFirst("id");
        return nameId != null ? Convert.ToInt32(nameId.Value) : 0;
      }
    
      public string GetUserName()
      {
        return _context.HttpContext.User.FindFirst("name")?.Value;
      }
    }
    
  7. 使用 Postman 進(jìn)行測試

    • 請求頭不添加 Authorization 腔召,返回 401 狀態(tài)碼:

      Unauthorized
  • 請求頭添加 Authorization扮惦,確保 Token 沒過期 :

    Success

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崖蜜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抡柿,更是在濱河造成了極大的恐慌等恐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪檬,死亡現(xiàn)場離奇詭異购笆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)样傍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門衫哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撤逢,你說我怎么就攤上這事〕跽” “怎么了互例?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腥光。 經(jīng)常有香客問我糊秆,道長,這世上最難降的妖魔是什么艘儒? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任夫偶,我火速辦了婚禮兵拢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘说铃。我一直安慰自己,他們只是感情好债热,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幼苛,像睡著了一般窒篱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天墙杯,我揣著相機(jī)與錄音配并,去河邊找鬼。 笑死高镐,一個胖子當(dāng)著我的面吹牛溉旋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫉髓,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼观腊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梧油?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤纱意,失蹤者是張志新(化名)和其女友劉穎婶溯,沒想到半個月后鲸阔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偷霉,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年褐筛,在試婚紗的時候發(fā)現(xiàn)自己被綠了类少。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡渔扎,死狀恐怖硫狞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晃痴,我是刑警寧澤残吩,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站倘核,受9級特大地震影響泣侮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜紧唱,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一活尊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漏益,春花似錦蛹锰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春翎苫,著一層夾襖步出監(jiān)牢的瞬間权埠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工煎谍, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留攘蔽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓呐粘,卻偏偏與公主長得像满俗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子作岖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353