Jwt隱藏大坑,通過源碼揭秘

前言

JWT是目前最為流行的接口認證方案之一,有關JWT協議的詳細內容,請參考:https://jwt.io/introduction

今天分享一下在使用JWT在項目中遇到的一個問題,主要是一個協議的細節,非常容易被忽略,如果不是自己遇到,或者去看源碼的實現,我估計至少80%的人都會栽在這里,下面來還原一下這個問題的過程,由于這個問題出現有一定的概率,不是每次都會出現,所以才容易掉坑里。

集成JWT

在Asp.Net Core中集成JWT認證的方式在網絡上隨便一搜就能找到一堆,主要有兩個步驟:

  1. 在IOC容器中注入依賴

public?void?ConfigureServices(IServiceCollection?services)
{//?添加這一行添加jwt驗證:services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options?=>?{options.TokenValidationParameters?=?new?TokenValidationParameters{ValidateIssuer?=?true,//是否驗證IssuerValidateAudience?=?true,//是否驗證AudienceValidateLifetime?=?true,//是否驗證失效時間ClockSkew?=?TimeSpan.FromSeconds(30),ValidateIssuerSigningKey?=?true,//是否驗證SecurityKeyValidAudience?=?Const.Domain,//AudienceValidIssuer?=?Const.Domain,//Issuer,這兩項和前面簽發jwt的設置一致IssuerSigningKey?=?new?SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey};});}
  1. 應用認證中間件

public?void?Configure(IApplicationBuilder?app,?IHostingEnvironment?env)
{//?添加這一行?使用認證中間件app.UseAuthentication();if?(env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseMvc(routes?=>{routes.MapRoute(name:?"default",template:?"{controller=Home}/{action=Index}/{id?}");});
}
  1. 在Controller

[Route("api/[controller]")]
[ApiController]?//?添加這一行
public?class?MyBaseController?:?ControllerBase
{}
  1. 提供一個認證的接口,用于前端獲取token

[AllowAnonymous]
[HttpGet]
public?IActionResult?Get(string?userName,?string?pwd)
{if?(!string.IsNullOrEmpty(userName)?&&?!string.IsNullOrEmpty(pwd)){var?claims?=?new[]{new?Claim(JwtRegisteredClaimNames.Nbf,$"{new?DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")?,new?Claim?(JwtRegisteredClaimNames.Exp,$"{new?DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),new?Claim(ClaimTypes.Name,?userName)};var?key?=?new?SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));var?creds?=?new?SigningCredentials(key,?SecurityAlgorithms.HmacSha256);var?token?=?new?JwtSecurityToken(issuer:?Const.Domain,audience:?Const.Domain,claims:?claims,expires:?DateTime.Now.AddMinutes(30),signingCredentials:?creds);return?Ok(new{token?=?new?JwtSecurityTokenHandler().WriteToken(token)});}else{return?BadRequest(new?{?message?=?"username?or?password?is?incorrect."?});}
}

至此,你的應用已經完成了集成JWT認證。

本文為Gui.H原創文章,更多高質量博文,歡迎關注公眾號dotnet之美

坑在哪里

直接上代碼,下面這段代碼是我用來能復現該大坑的示例,有空的可以按照該代碼重現下面的問題。

using?Microsoft.IdentityModel.Tokens;
using?System.IdentityModel.Tokens.Jwt;
using?System.Security.Claims;
using?System.Text;var?SecurityKey?=?"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB";
var?Domain?=?"http://localhost:5000";var?email?=?"username@qq.com";
var?userName?=?"阿哈";var?claims?=?new[]
{new?Claim(JwtRegisteredClaimNames.Nbf,$"{new?DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")?,new?Claim?(JwtRegisteredClaimNames.Exp,$"{new?DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),new?Claim("Name",?userName),new?Claim("Email",?email),};var?key?=?new?SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey));
var?creds?=?new?SigningCredentials(key,?SecurityAlgorithms.HmacSha256);
var?token?=?new?JwtSecurityToken(issuer:?Domain,audience:?Domain,claims:?claims,expires:?DateTime.Now.AddMinutes(30),signingCredentials:?creds);var?JWTToken?=?new?JwtSecurityTokenHandler().WriteToken(token);Console.WriteLine(JWTToken);Console.ReadLine();

上面代碼運行的結果是:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.RBtP7zroK7YueGlDdZNHGy3tT8-xcGkf8ZyiTL81w2I

我們知道Token由三部分組成,使用.分割,如果是標準的Jwt協議加密的,那這三部分均為Base64加密(此處不準確,下文解釋為什么),也可以說就是明文,我們將三部分內容進行Base64解密看看。

我們在線驗證一下我們的Jwt是否符合標準:打開網站:https://jwt.io/,選擇頂部菜單的Debugger,將我們的token填進去:

d1773ec239240a2acab6800249d12166.png然后將代碼中用的SecurityKey填到圖中標記的位置

b408e4145d71cb20a7d2526db00dde17.png顯示簽名認證通過。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9
{??"alg":?"HS256",??"typ":?"JWT",??"cty":?"JWT"?}

載荷

eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ
{"nbf":?"1653400694","exp":?1653402494,"Name":?"阿哈","Email":?"username@qq.com","iss":?"http://localhost:5000","aud":?"http://localhost:5000"
}

簽名

RBtP7zroK7YueGlDdZNHGy3tT8-xcGkf8ZyiTL81w2I

到目前為止一切都十分順利。

既然Token的內容前端直接可以通過base64解密出來,那在需要展示用戶名的地方,我們就可以直接解析token的載荷,然后獲得Name,下面是使用在線base64工具解密上面的token載荷內容,可以看到用戶名為啊哈4e0d4b92ffcc95a3bbe95ec8afcedda5.png邏輯沒有任何問題,那就開始前端進行解析token中的用戶名用于展示在個人中心吧。下面是在Vue3框架和Piana中的演示,window.atob是瀏覽器自帶base64decode的方法

export?const?useUserStore?=?defineStore({id:?'user',state:?()?=>?{return?{token:?'',}},getters:?{accessToken:?(state)?=>?{return?state.accesstoken?||?localStorage.getItem("accesstoken");},/***?獲取token中解密后的用戶信息*/userInfo(state)?{var?token?=?state.token?||?localStorage.getItem("accesstoken");if?(!token?||?token?==?'')?{return?null;}var?json?=?window.atob(token.split(".")[1]);return?JSON.parse(json);}}
})

在需要獲取用戶名的地方使用

computed:{...mapState(useUserStore,?["userInfo"]),
}

感覺一切都很優雅的寫完了代碼,但是實際運行會報錯:這里為了方便是直接在瀏覽器的調式器中執行的b7b3e4a8bd88a368393de17ad573d5de.png報錯的意思來看是說我們的字符串沒用正確的加密(就是它說咱這個字符串不是合法的base64加密)。可是我們通過一些在線base64解密工具,還有Jwt的debugger工具都能解密出來明文。而且這不是我第一次將token拿出來進行解密了,之前也都沒問題。

  1. 是不是token有問題?經過測試,調用接口完全不會有問題,只是前端解密時報錯,排除token不合法。

  2. 前端的atob函數存在bug?那我們在后端用c#的base64解密一下看看:f5085b7d8a04d0574c268f4fd4008ffe.png居然后端解密也報錯了,頭部解密成功,載荷部分解密異常,和前端報錯一樣都是說字符串不是合法的base64內容,不知道你是不是偶爾遇到過這個問題,如果沒有,那你更要往下看了,不然以后遇到了,要耽誤不少時間去排查了。

查看源碼探索問題原因

上面遇到的問題曾經花了我不少時間去排查,關鍵是有工具能解密的還有工具不能解密,一時不知道到底是誰的問題了,抱著試試看的態度,看看源碼生成token三部分的字符串過程。

  1. 既然token是這個函數生成的,那就直接看它的實現,直接F12即可,這個包是不是框架自帶的,所以能直接通過vs看源碼,比較方便的。03f5896a738853ec371f863c4bef1874.png

  2. 源碼如下,encodedPayload根據它的命名不難看出是機密后的載荷,我們需要看的是它如何加密的6cdda9e910eb080f0ba5ac8904b2ef0c.png

  3. 查看jwtToken.EncodedPayload這個屬性怎么來的(F12)07d61d1981b645de07d7eb71895a2aa3.png圖中標記了三個數字:

  1. 上一步我們逆向找到加密后的屬性EncodedPayload

  1. EncodedPayload屬性里面用到了另一個屬性Payload,我們需要找Payload哪里賦值的

  1. Payload是在構造函數中根據傳參內容進行初始化的。

  1. 上一步我們已經鎖定進加密的邏輯在Payload.Base64UrlEncode()中,看JwtPayload的類定義

659d7394b47355e5d6505908bb4960bd.png可以看出,載荷的加密和我們想象的一樣簡單,把JwtPayload對象轉成Json,然后進行Base64Url加密 5. 現在只剩Base64UrlEncoder.Encode的實現能為我們揭秘了a48180c727eb2bfe3be278b77e7b0312.png整體看下類定義,我們調用的Encode按標記順序,依次調用了三個重載方法,最終實現都標記為3的那個方法。6. 不知道你有沒有注意到這些內容ab5371206fe8f31d6e82699ab7f673e8.png看到這里我恍然大悟了一點,再看看他這里面的decode方法

bbfe4eacdde3caf5ced4114509ec69f6.png看見了吧,我們因為是單純的Base64加解密,其實不然,在進行Convert.FromBase64String(decodedString)解密前還需要進行一些字符串的替換,我趕緊看下上面出問題的載荷內容,發現其中有_這個字符,我趕緊將其進行替換成+,再次嘗試:

eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ//?替換后
eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi+5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ

果然如此,替換后解密成功了,只有一個漢字的編碼問題。e4256fea25a36b85bf797dca6ce1d730.png

這下找到問題了,優化下前端的解密代碼

userInfo(state)?{var?token?=?state.token?||?localStorage.getItem("accesstoken");if?(!token?||?token?==?'')?{return?null;}token?=?token.replace("_",?"/").replace("-",?"+")?//?添加這一行var?json?=?window.atob(token.split(".")[1]);return?JSON.parse(json);}

問題解決了。

注意官方對加密過程的描述

fc6d3dcadb6a245459ebc43719dc2ed6.png哈哈,是不是草率了,并不是Base64加密~~

總結

我們都以為Jwt三部分是用Base64加密,其實不完全對,因為他確切的加密方式是Base64Url加密,沒有深入理解的我們只以為就是純粹的base64,而且在大部分情況下確實是這樣,更加堅定了我們這種錯誤認知。而只有當Base64加密后出現字符+/時,才會有所不同,希望對大家有幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287065.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287065.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287065.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

GPS實驗二:GPS接收機的使用

一、實習目的 1、了解GPS接收機的基本結構; 2、掌握GPS接收機的一般操作方法。 二、實習內容 1、了解GPS接收機的外觀及主要構成單元; 2、學習GPS接收機的安裝及靜態測量的操作方法; 3、了解GPS接收機工作時的基本狀態信息。 三、實習地點 選擇視野開闊的場所,視場…

Android之解決CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+RecyclerView里面再嵌套RecyclerView滑動顫抖問題

1 問題 主頁面用的是這種結構 CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+RecyclerView(里面再嵌套RecyclerView,然后這個RecyclerView再嵌套一個RecyclerView)上下滑動在AppBarLayout下面出現頁面上下顫抖問題 2 分析 我的主頁代碼如下 <?xml versio…

文件傳輸基礎——Java IO流

一、文件的編碼 1 package com.study.io;2 3 4 /**5 * 測試文件編碼6 */7 public class EncodeDemo {8 9 /** 10 * param args 11 * throws Exception 12 */ 13 public static void main(String[] args) throws Exception { 14 String s&quo…

keepalived實現nginx的高可用(雙主模型)

實驗環境&#xff1a;RS1&#xff1a;rip&#xff08;172.16.125.7&#xff09;&#xff0c;安裝httpd軟件包&#xff1b;RS2&#xff1a;rip&#xff08;172.16.125.8&#xff09;&#xff0c;安裝httpd軟件包&#xff1b;director1&#xff08;7-1.lcs.com&#xff09;&#…

【必懂C++】第一個程序當然是HelloWorld呀 01

作者簡介 作者名&#xff1a;1_bit 簡介&#xff1a;CSDN博客專家&#xff0c;2020年博客之星TOP5&#xff0c;藍橋簽約作者。15-16年曾在網上直播&#xff0c;帶領一批程序小白走上程序員之路。歡迎各位小白加我咨詢我相關信息&#xff0c;迷茫的你會找到答案。系列教程將會…

實現html5音樂的自動播放,html5中audio實現播放列表和自動播放

var count 43; //一共多少MP3文件var index 18.mp3; // 初始化播放那個文件window.onload function(){var audio new Audio();audio.preload true;audio.controls true;audio.loop false;audio.src index;document.body.appendChild(audio);audio.play();audio.addEven…

GPS實驗三:GPS接收機野外數據采集

一、實習目的 1、掌握GPS接收機的使用方法; 2、學會量取天線高 3、掌握選點和埋設標志的原則 二、實習內容 1、了解GPS接收機的外觀及主要構成單元; 2、學習GPS接收機的安裝及靜態測量的操作方法; 3、了解GPS接收機工作時的基本狀態信息。 三、實習地點 選擇視野開闊的…

打造操作系統根社區 統信Deepin屹立于浪潮之顛

如果把芯片比作信息系統的大腦的話&#xff0c;那么操作系統毫無疑問就是信息系統的靈魂。在過去幾十年里&#xff0c;我國信息產業飽受“缺芯少魂”的困擾&#xff0c;國內市場基本被微軟、谷歌、蘋果、IBM、紅帽等外商壟斷。誠然&#xff0c;一些國內廠商推出過基于Fedora、u…

Androd之在圖片右上角顯示紅色圓圈里面數字提醒

1 需求 在圖片右上角顯示紅色圓圈里面數字提醒 2 效果如圖 3 關鍵代碼 item_loca.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_wid…

Bean

Bean spring中把一切配置到IOC容器(其實就是那個xml文件)里面的對象都稱之為bean。 轉載于:https://www.cnblogs.com/Renyi-Fan/p/7780935.html

html原樣輸出html代碼

<xmp>********</xmp> 在網頁上顯示html代碼標記<xmp></xmp>有時我們會將html代碼顯示在網頁上,直接寫會有問題, 如果我們將要顯示的html代碼放在<xmp></xmp>中就可以實現轉載于:https://www.cnblogs.com/sign-ptk/p/5668442.html

ArcGIS實驗教程——實驗二十二:空間數據符號化

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 一、實驗描述 空間數據可視化是通過地圖語言實現的,地圖語言由符號、色彩和文字注記組成。 地圖符號由形狀不同、大小不一、色彩有別的圖形和文字組成,是地圖語言的圖解部分。 符號化是以圖形方…

【必懂C++】C++可真是個“固執”的小可愛 02

作者簡介 作者名&#xff1a;1_bit 簡介&#xff1a;CSDN博客專家&#xff0c;2020年博客之星TOP5&#xff0c;藍橋簽約作者。15-16年曾在網上直播&#xff0c;帶領一批程序小白走上程序員之路。歡迎各位小白加我咨詢我相關信息&#xff0c;迷茫的你會找到答案。系列教程將會…

html td顯示隱藏,顯示/隱藏Html TR/TD

如何從html表中顯示/隱藏任何tr/td&#xff1f; 我應該可以隱藏表格中顯示的任何tr/td。顯示/隱藏Html TR/TDCountryUSASwedenNorwayInventory ID$123.23Cost$312.21Descriptionthis is a descriptionCSS&#xff1a;#tableEditor {position: absolute;left: 20px;top: 20px;pa…

Flutter之window系統下配置開發環境以及在Android Studio里面運行hello word

1 、window系統配置Flutter開發環境 1&#xff09;下載Flutter的SDK 如果電腦安裝了Git&#xff0c;直接到https://github.com/flutter/flutter/這里下載&#xff0c;但是需要翻墻 git clone https://github.com/flutter/flutter.git 或者到lutter官網下載 https://flutter.d…

java1.8--改進的接口

關于接口&#xff0c;每天的編碼都在寫&#xff0c;就不多說了。這里對比下接口&#xff0c;抽象類&#xff0c;類3者的關系&#xff1a; 1&#xff09;&#xff0c;接口是一種規范&#xff0c;就是告訴外界這個東東可以做什么。 2&#xff09;&#xff0c;抽象類是一種模板&am…

WPF效果第一百八十五篇之又玩TreeView

最近又有新的開發任務了,然后我提前瞄了一眼需要實現的效果;發現其中一個和我去年玩耍的有點類似;正好好久也沒玩了,那就趁著這個機會再次學習一下;閑話也不多扯了,上效果:2、來看看我的實現方式:3、①是一個分組的數據模板<HierarchicalDataTemplate x:Key"GroupDataT…

ArcGIS實驗教程——實驗二十三:專題地圖制作完整實驗步驟

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 一、實驗描述 專題地圖是一個非常復雜的過程,地圖數據的符號化與注記標注,都是地圖編制準備基礎的地理數據。然而,要將準備好的地圖數據,通過一幅完整的地圖表達出來,還有很多工作,包括布局…

IOS 封裝輪播圖

輪播圖為一種常見的方式&#xff0c;常用于各種網站&#xff0c;或者App中&#xff0c;當然&#xff0c;作為APP的啟動視圖也是不錯的選擇。 閑時封裝了一個&#xff0c;僅供新手參考。 1.新建工程&#xff0c;建立輪播圖類 建立一個空的工程&#xff0c;新建一個類&#xff0c…

分布式事務TCC補償機制

文章目錄 概述工作流程優缺點優點&#xff1a;缺點&#xff1a; 總結Java 示例代碼 概述 TCC&#xff08;Try-Confirm-Cancel&#xff09;補償機制是一種事務處理模式&#xff0c;用于確保分布式系統中的操作成功完成或在失敗時進行補償。TCC將一個事務拆分為三個階段&#xf…