摘要:利用消息認證碼可以確保消息不是被別人偽造的,消息認證碼是帶密鑰的函數,由于有了一個,所以會比有更好的安全性。所以需要采用的就是算法,該算法主要利用的是不對稱加密算法,利用私鑰進行簽名,公鑰驗證數據的完整性。
寫在前面
本文會到你了解jwt的實現原理,以及base64編碼的原理。同時本人也簡單的實現了一下jwt的生成,點這里。
jwt是什么為什么需要jwt本質上它是一段簽名的 JSON 格式的數據。由于它是帶有簽名的,因此接收者便可以驗證它的真實性。同時由于它是 JSON 格式的因此它的體積也很小。
JSON Web Token (JWT)是一種開放標準(RFC 7519),其中定義了一種緊湊 (compact) 且自包含(self-contained)的方式用于以JSON對象的形式在多方之間傳遞信息。信息可以被核實和信任,因為它經過了數字簽名。JWT既可以使用密鑰(采用HMAC算法),也可以使用公私鑰(采用RSA算法)進行簽名。
http的協議是無狀態的,所有很長一段時間內,我們利用session/cookie來客服,服務器端存儲session,客戶端存儲一個sessionid,訪問時客戶端帶著sessionid,服務器根據對應的session,來確定是否該用戶是否有相應的權限以及如何展現這個頁面;但是目前隨著終端設備的增多,比較流行的開發模式為前后端分離,也就是說后端趨向于服務化,提供相應操作的接口,RESTful API是目前比較成熟的一套接口規范;而RESTful API倡導的就是無狀態,而無狀態可以利用今天所說的jwt來實現。session這種有狀態的方式,要大量的占用服務器的內存,同時當項目很大時,可能需要借助于redis集群來存儲session。利用jwt可以將用戶狀態權限等放到客戶端,服務端根據傳過去的token來判斷是否有訪問這個資源的權限。
jwt的組成jwt包含了三部分,用.進行分隔:
頭部(header)
載荷(payload)
簽名(Signature)
下面來一步步的生成這個token,先利用一個數組來保存這三個部門,聲明一個數組const res = [];,得到三個部分后,利用res.join(".")來生成所需的token就好。
頭部頭部中很一般包含兩部分,token的類型與采用的加密算法,如:
const header = { alg: "HS256", typ: "JWT" };
而后將,這個進行序列化并且轉化為base64編碼,需要注意的是jwt中對應的base64并不是一個嚴格意義上的base64,由于token有可能被做為url,而base64中的+/=三個字符會被轉義,導致url變得更長,所以token的base64會將+轉化為-、/轉化為_、刪除=。
根據這個規則,來實現一下生成符合要求的base64的函數:
const getBase64UrlEscape = str => ( str.replace(/+/g, "-") .replace(///g, "_") .replace(/=/g, "") ); const getBase64Url = data => getBase64UrlEscape( new Buffer(JSON.stringify(data)).toString("base64") );
實現了這個公共函數后,生成header就變得很簡單了:
res.push(getBase64Url(header));載荷
載荷中包含了聲明,聲明是對于用戶的敘述以及其他的元數據。含有三種類型的聲明:
保留聲明,如exp,sub等
公有聲明
私有聲明,私有聲明為自定義的
對于載荷來說,個人傾向于在里面存儲一些無關緊要的東西,如用戶名,用戶權限,用戶id等:
const payload = { username: "zp1996", id: 1, authority: 32 };
第二部分就是將payload轉化為base64編碼:
res.push(getBase64Url(payload));簽名
簽名就是將編碼后的頭部、載荷,利用相應的密鑰應用相應的加密算法進行加密:
sha256( `${base64UrlEncode(header)}.${base64UrlEncode(payload)}`, secret )
支持的算法一般有:
const algorithmMap = { HS256: "sha256", HS384: "sha384", HS512: "sha512", RS256: "RSA-SHA256" }; const typeMap = { HS256: "hmac", HS384: "hmac", HS512: "hmac", RS256: "sign" };
首先先來了解一下關于加密的幾種算法:
Hash加密—crypto.createHash()
通過散列可以把任意長度的輸入轉化為固定長度的輸出,輸出的值就為散列值。如果兩個散列值是不相同的,那么原始輸入一定不相同;但是兩個散列值是相同的,只能說原始輸入有很大的可能是相同的;因為散列函數有可能會發生“碰撞”。hash很快,問題也在于很快。雖說加密之后不可以逆推,但是可以通過彩虹表的方式來破解,由于hash是很快的,根據彩虹表來與待破解的加密字符進行比對,很快就會得出我們想要的結果。當然一般的會進行加鹽的操作,來增加這個時間成本。回到今天說的jwt,jwt對于安全的要求還是很高的,jwt的前兩個部分就是一個經過加工的base64,肯定可以轉義出來的,假設我們最后一個的簽名部分都被解密出來了,那么token就可以被任意偽造,應用也就沒有了安全性可言。所以jwt在生成簽名時并沒有采取Hash加密。
Hmac加密—crypto.createHmac()
先來看一下hmac的高大上的定義:密鑰相關的哈希運算消息認證碼。利用消息認證碼可以確保消息不是被別人偽造的,消息認證碼是帶密鑰的hash函數,由于hmac有了一個key,所以會比hash有更好的安全性。
Sign加密—crypto.createSign()
除了對數據進行加密和解密外,還需要對于數據傳輸過程中的完整性,安全性是否得到了保證。所以需要采用的就是Sign算法,該算法主要利用的是不對稱加密算法,利用私鑰進行簽名,公鑰驗證數據的完整性。整個過程可以參見下圖:
私鑰和公鑰利用openssl來生成,下面來看一個例子:
const fs = require("fs"), crypto = require("crypto"), data = "zp1996", alg = "RSA-SHA256"; const signer = (method, key, input) => crypto.createSign(method) .update(input) .sign(key, "base64"); const verify = (method, pub, sign, input) => crypto.createVerify(method) .update(input) .verify(pub, sign, "base64"); const sign = signer(alg, fs.readFileSync("./private.pem"), data); console.log(verify( alg, fs.readFileSync("./public.pem"), sign, data )); // true
jwt中主要利用了hmac與sign,于是我們就可以寫出生成簽名的方法:
const cryptoMethod = { hmac: (method, key, input) => crypto.createHmac(method, key).update(input).digest("base64"), sign: (method, input) => crypto.createSign(method).update(input).sign(key, "base64") };
至此,我們就可以寫出一個完整的sign方法了:
const sign = (input, key, method, type) => getBase64UrlEscape( cryptoMethod[type](method, key, input) ); /* * payload 載荷 * key 密鑰 * algorithm 加密算法 * type 采用何種類型 hmac or sign */ jwt.sign = (payload, key, algorithm = "HS256", options = {}) => { const signMethod = algorithmMap[algorithm], signType = typeMap[algorithm], header = { typ: "JWT", alg: algorithm }, res = []; options && options.header && Object.assign(header, options.header); res.push(getBase64Url(header)); res.push(getBase64Url(payload)); res.push(sign(res.join("."), key, signMethod, signType)); return res.join("."); };
至此就可生成一個比較合格的token了,下面就來實現驗證與解析:
解析解析出header和payload,這件事說白了其實就是將base64轉化為普通字符串,而后再將字符串反序列化就可以得出。難點在與token的base64是被加工過得,對于替換的情況很容易轉化回去,利用正則很容易做到,但是對于去除的=要怎么補回去呢?為什么就要去除這個=呢?它有什么特殊的地方嗎?我想,有必要來看一下base64的原理:
base64原理 base64字符集base64是一種基于64個可打印字符來表示二進制數據的方法,包括A-Z、a-z、0-9、+、/,以及一個補位用的=,實際上來看是65個字符,來看一張base64索引表:
基本原理:將每3個8字節轉換為4個6字節,然后將轉換后的4個6字節高位添加2個0,組成4個8字節,所以base64要比原字符串的大1/3左右,來看一個例子,如何將zpy轉化為base64編碼:
首先確定ascii碼,分別為122,112,121,由于只有兩個字符,所以后面的一個字符對應的二進制為00000000,于是構成的24位為:
01111010 | 01110000 | 01111001
以六位為一個部分進行分割:
011110 | 100111 | 000001 | 111001
對高位進行補0的操作,再將其轉化為10進制:
30 | 39 | 1 | 57
在結合上圖的索引表來看,很容易得出最終的值為enB5,由這個規則來看,我們很容易得出的是zp為enAA,但是利用new Buffer("zp").toString("base64")得出的為enA=,仔細來看base64還有兩項規則:
兩個字節的情況:將這兩個字節的一共16個二進制位,按照上面的規則,轉成三組,最后一組除了前面加兩個0以外,后面也要加兩個0。這樣得到一個三位的Base64編碼,再在末尾補上一個"="號。
一個字節的情況:將這一個字節的8個二進制位,按照上面的規則轉成二組,最后一組除了前面加二個0以外,后面再加4個0。這樣得到一個二位的Base64編碼,再在末尾補上兩個"="號。
解析tokenbase64的原理搞清楚了,那如何給token添加=也就很明白了,無外乎是在結尾加一個還是兩個。base64編碼長度一定是4的倍數,所以只需要在%4之后進行加=操作就好,結果只可能有兩種情況:
2,添加兩個=
3,添加一個=
所以可以寫出下面代碼來將token轉化為一個真正的base64:
const getBase64UrlUnescape = str => { str += new Array(5 - str.length % 4).join("="); return str.replace(/-/g, "+") .replace(/\_/g, "/"); };
到這里,解析方法就可以很容易的出來了:
const decodeBase64Url = str => JSON.parse( new Buffer(getBase64UrlUnescape(str), "base64").toString() ); jwt.decode = (token) => { const segments = token.split("."); return { header: decodeBase64Url(segments[0]), payload: decodeBase64Url(segments[1]) }; };驗證
前面基本都理解了,驗證其實很簡單,就是利用密鑰,來比對,看是否正確驗證:
const verifyMethod = { hmac: (input, key, method, signStr) => signStr === sign(input, key, method, "hmac"), sign: (input, key, method, sign) => { return crypto.createVerify(method) .update(input) .verify(key, getBase64UrlUnescape(sign), "base64"); } };寫在最后
本文只是本人的一點愚見,如有錯誤,歡迎大家指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82434.html
摘要:什么是官方給出的定義是是一個開放標準,它定義了一種緊湊的獨立的方式,用于安全地在當事人之間傳遞信息作為一個對象。結構由下面三個部分組成一個字符串通常由兩個部分組成令牌的,即,以及正在使用的散列算法,如或。 什么是JSON Web Token ? 官方給出的定義是:JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、獨立的方式,用于安全地在當事人...
摘要:用于方便地搭建能夠處理超高并發擴展性極高的動態應用服務和動態網關。 介紹 權限認證是接口開發中不可避免的問題,權限認證包括兩個方面 接口需要知道調用的用戶是誰 接口需要知道該用戶是否有權限調用 第1個問題偏向于架構,第2個問題更偏向于業務,因此考慮在架構層解決第1個問題,以達到以下目的 所有請求被保護的接口保證是合法的(已經認證過的用戶) 接口可以從請求頭中獲取當前用戶信息 每個...
閱讀 2001·2019-08-29 16:27
閱讀 1370·2019-08-29 16:14
閱讀 3372·2019-08-29 14:18
閱讀 3455·2019-08-29 13:56
閱讀 1252·2019-08-29 11:13
閱讀 2118·2019-08-28 18:19
閱讀 3439·2019-08-27 10:57
閱讀 2273·2019-08-26 11:39