国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

手動(dòng)實(shí)現(xiàn)一個(gè)jsonwebtoken

zhangke3016 / 1211人閱讀

摘要:利用消息認(rèn)證碼可以確保消息不是被別人偽造的,消息認(rèn)證碼是帶密鑰的函數(shù),由于有了一個(gè),所以會(huì)比有更好的安全性。所以需要采用的就是算法,該算法主要利用的是不對稱加密算法,利用私鑰進(jìn)行簽名,公鑰驗(yàn)證數(shù)據(jù)的完整性。

寫在前面

本文會(huì)到你了解jwt的實(shí)現(xiàn)原理,以及base64編碼的原理。同時(shí)本人也簡單的實(shí)現(xiàn)了一下jwt的生成,點(diǎn)這里。

jwt是什么

本質(zhì)上它是一段簽名的 JSON 格式的數(shù)據(jù)。由于它是帶有簽名的,因此接收者便可以驗(yàn)證它的真實(shí)性。同時(shí)由于它是 JSON 格式的因此它的體積也很小。
JSON Web Token (JWT)是一種開放標(biāo)準(zhǔn)(RFC 7519),其中定義了一種緊湊 (compact) 且自包含(self-contained)的方式用于以JSON對象的形式在多方之間傳遞信息。信息可以被核實(shí)和信任,因?yàn)樗?jīng)過了數(shù)字簽名。JWT既可以使用密鑰(采用HMAC算法),也可以使用公私鑰(采用RSA算法)進(jìn)行簽名。

為什么需要jwt

http的協(xié)議是無狀態(tài)的,所有很長一段時(shí)間內(nèi),我們利用session/cookie來客服,服務(wù)器端存儲(chǔ)session,客戶端存儲(chǔ)一個(gè)sessionid,訪問時(shí)客戶端帶著sessionid,服務(wù)器根據(jù)對應(yīng)的session,來確定是否該用戶是否有相應(yīng)的權(quán)限以及如何展現(xiàn)這個(gè)頁面;但是目前隨著終端設(shè)備的增多,比較流行的開發(fā)模式為前后端分離,也就是說后端趨向于服務(wù)化,提供相應(yīng)操作的接口,RESTful API是目前比較成熟的一套接口規(guī)范;而RESTful API倡導(dǎo)的就是無狀態(tài),而無狀態(tài)可以利用今天所說的jwt來實(shí)現(xiàn)。session這種有狀態(tài)的方式,要大量的占用服務(wù)器的內(nèi)存,同時(shí)當(dāng)項(xiàng)目很大時(shí),可能需要借助于redis集群來存儲(chǔ)session。利用jwt可以將用戶狀態(tài)權(quán)限等放到客戶端,服務(wù)端根據(jù)傳過去的token來判斷是否有訪問這個(gè)資源的權(quán)限。

jwt的組成

jwt包含了三部分,用.進(jìn)行分隔:

頭部(header)

載荷(payload)

簽名(Signature)

下面來一步步的生成這個(gè)token,先利用一個(gè)數(shù)組來保存這三個(gè)部門,聲明一個(gè)數(shù)組const res = [];,得到三個(gè)部分后,利用res.join(".")來生成所需的token就好。

頭部

頭部中很一般包含兩部分,token的類型與采用的加密算法,如:

const header = {
    alg: "HS256",
    typ: "JWT"
};

而后將,這個(gè)進(jìn)行序列化并且轉(zhuǎn)化為base64編碼,需要注意的是jwt中對應(yīng)的base64并不是一個(gè)嚴(yán)格意義上的base64,由于token有可能被做為url,而base64中的+/=三個(gè)字符會(huì)被轉(zhuǎn)義,導(dǎo)致url變得更長,所以tokenbase64會(huì)將+轉(zhuǎn)化為-/轉(zhuǎn)化為_、刪除=
根據(jù)這個(gè)規(guī)則,來實(shí)現(xiàn)一下生成符合要求的base64的函數(shù):

const getBase64UrlEscape = str => (
  str.replace(/+/g, "-")
    .replace(///g, "_")
    .replace(/=/g, "")
);

const getBase64Url = data => getBase64UrlEscape(
  new Buffer(JSON.stringify(data)).toString("base64")
);

實(shí)現(xiàn)了這個(gè)公共函數(shù)后,生成header就變得很簡單了:

res.push(getBase64Url(header));
載荷

載荷中包含了聲明,聲明是對于用戶的敘述以及其他的元數(shù)據(jù)。含有三種類型的聲明:

保留聲明,如expsub

公有聲明

私有聲明,私有聲明為自定義的

對于載荷來說,個(gè)人傾向于在里面存儲(chǔ)一些無關(guān)緊要的東西,如用戶名,用戶權(quán)限,用戶id等:

const payload = {
    username: "zp1996",
    id: 1,
    authority: 32
};

第二部分就是將payload轉(zhuǎn)化為base64編碼:

res.push(getBase64Url(payload));
簽名

簽名就是將編碼后的頭部、載荷,利用相應(yīng)的密鑰應(yīng)用相應(yīng)的加密算法進(jìn)行加密:

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"
};

首先先來了解一下關(guān)于加密的幾種算法:

Hash加密—crypto.createHash()

通過散列可以把任意長度的輸入轉(zhuǎn)化為固定長度的輸出,輸出的值就為散列值。如果兩個(gè)散列值是不相同的,那么原始輸入一定不相同;但是兩個(gè)散列值是相同的,只能說原始輸入有很大的可能是相同的;因?yàn)樯⒘泻瘮?shù)有可能會(huì)發(fā)生“碰撞”。hash很快,問題也在于很快。雖說加密之后不可以逆推,但是可以通過彩虹表的方式來破解,由于hash是很快的,根據(jù)彩虹表來與待破解的加密字符進(jìn)行比對,很快就會(huì)得出我們想要的結(jié)果。當(dāng)然一般的會(huì)進(jìn)行加鹽的操作,來增加這個(gè)時(shí)間成本。回到今天說的jwtjwt對于安全的要求還是很高的,jwt的前兩個(gè)部分就是一個(gè)經(jīng)過加工的base64,肯定可以轉(zhuǎn)義出來的,假設(shè)我們最后一個(gè)的簽名部分都被解密出來了,那么token就可以被任意偽造,應(yīng)用也就沒有了安全性可言。所以jwt在生成簽名時(shí)并沒有采取Hash加密。

Hmac加密—crypto.createHmac()

先來看一下hmac的高大上的定義:密鑰相關(guān)的哈希運(yùn)算消息認(rèn)證碼。利用消息認(rèn)證碼可以確保消息不是被別人偽造的,消息認(rèn)證碼是帶密鑰的hash函數(shù),由于hmac有了一個(gè)key,所以會(huì)比hash有更好的安全性。

Sign加密—crypto.createSign()

除了對數(shù)據(jù)進(jìn)行加密和解密外,還需要對于數(shù)據(jù)傳輸過程中的完整性,安全性是否得到了保證。所以需要采用的就是Sign算法,該算法主要利用的是不對稱加密算法,利用私鑰進(jìn)行簽名,公鑰驗(yàn)證數(shù)據(jù)的完整性。整個(gè)過程可以參見下圖:

私鑰和公鑰利用openssl來生成,下面來看一個(gè)例子:

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中生成簽名的方式

jwt中主要利用了hmacsign,于是我們就可以寫出生成簽名的方法:

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")
};

至此,我們就可以寫出一個(gè)完整的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(".");
};

至此就可生成一個(gè)比較合格的token了,下面就來實(shí)現(xiàn)驗(yàn)證與解析:

解析

解析出headerpayload,這件事說白了其實(shí)就是將base64轉(zhuǎn)化為普通字符串,而后再將字符串反序列化就可以得出。難點(diǎn)在與tokenbase64是被加工過得,對于替換的情況很容易轉(zhuǎn)化回去,利用正則很容易做到,但是對于去除的=要怎么補(bǔ)回去呢?為什么就要去除這個(gè)=呢?它有什么特殊的地方嗎?我想,有必要來看一下base64的原理:

base64原理 base64字符集

base64是一種基于64個(gè)可打印字符來表示二進(jìn)制數(shù)據(jù)的方法,包括A-Z、a-z、0-9、+、/,以及一個(gè)補(bǔ)位用的=,實(shí)際上來看是65個(gè)字符,來看一張base64索引表:

基本原理:

將每3個(gè)8字節(jié)轉(zhuǎn)換為4個(gè)6字節(jié),然后將轉(zhuǎn)換后的4個(gè)6字節(jié)高位添加2個(gè)0,組成4個(gè)8字節(jié),所以base64要比原字符串的大1/3左右,來看一個(gè)例子,如何將zpy轉(zhuǎn)化為base64編碼:

首先確定ascii碼,分別為122,112,121,由于只有兩個(gè)字符,所以后面的一個(gè)字符對應(yīng)的二進(jìn)制為00000000,于是構(gòu)成的24位為:

01111010 | 01110000 | 01111001

以六位為一個(gè)部分進(jìn)行分割:

011110 | 100111 | 000001 | 111001

對高位進(jìn)行補(bǔ)0的操作,再將其轉(zhuǎn)化為10進(jìn)制:

30 | 39 | 1 | 57

在結(jié)合上圖的索引表來看,很容易得出最終的值為enB5,由這個(gè)規(guī)則來看,我們很容易得出的是zpenAA,但是利用new Buffer("zp").toString("base64")得出的為enA=,仔細(xì)來看base64還有兩項(xiàng)規(guī)則:

兩個(gè)字節(jié)的情況:將這兩個(gè)字節(jié)的一共16個(gè)二進(jìn)制位,按照上面的規(guī)則,轉(zhuǎn)成三組,最后一組除了前面加兩個(gè)0以外,后面也要加兩個(gè)0。這樣得到一個(gè)三位的Base64編碼,再在末尾補(bǔ)上一個(gè)"="號。

一個(gè)字節(jié)的情況:將這一個(gè)字節(jié)的8個(gè)二進(jìn)制位,按照上面的規(guī)則轉(zhuǎn)成二組,最后一組除了前面加二個(gè)0以外,后面再加4個(gè)0。這樣得到一個(gè)二位的Base64編碼,再在末尾補(bǔ)上兩個(gè)"="號。

解析token

base64的原理搞清楚了,那如何給token添加=也就很明白了,無外乎是在結(jié)尾加一個(gè)還是兩個(gè)。base64編碼長度一定是4的倍數(shù),所以只需要在%4之后進(jìn)行加=操作就好,結(jié)果只可能有兩種情況:

2,添加兩個(gè)=

3,添加一個(gè)=

所以可以寫出下面代碼來將token轉(zhuǎn)化為一個(gè)真正的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])
  };
};
驗(yàn)證

前面基本都理解了,驗(yàn)證其實(shí)很簡單,就是利用密鑰,來比對,看是否正確驗(yàn)證:

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");
  }
};
寫在最后

本文只是本人的一點(diǎn)愚見,如有錯(cuò)誤,歡迎大家指出。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82434.html

相關(guān)文章

  • 基于jsonwebtoken(JWT) 的web認(rèn)證 (Node版實(shí)現(xiàn))

    摘要:什么是官方給出的定義是是一個(gè)開放標(biāo)準(zhǔn),它定義了一種緊湊的獨(dú)立的方式,用于安全地在當(dāng)事人之間傳遞信息作為一個(gè)對象。結(jié)構(gòu)由下面三個(gè)部分組成一個(gè)字符串通常由兩個(gè)部分組成令牌的,即,以及正在使用的散列算法,如或。 什么是JSON Web Token ? 官方給出的定義是:JSON Web Token(JWT)是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊的、獨(dú)立的方式,用于安全地在當(dāng)事人...

    baishancloud 評論0 收藏0
  • Nginx實(shí)現(xiàn)JWT驗(yàn)證-基于OpenResty實(shí)現(xiàn)

    摘要:用于方便地搭建能夠處理超高并發(fā)擴(kuò)展性極高的動(dòng)態(tài)應(yīng)用服務(wù)和動(dòng)態(tài)網(wǎng)關(guān)。 介紹 權(quán)限認(rèn)證是接口開發(fā)中不可避免的問題,權(quán)限認(rèn)證包括兩個(gè)方面 接口需要知道調(diào)用的用戶是誰 接口需要知道該用戶是否有權(quán)限調(diào)用 第1個(gè)問題偏向于架構(gòu),第2個(gè)問題更偏向于業(yè)務(wù),因此考慮在架構(gòu)層解決第1個(gè)問題,以達(dá)到以下目的 所有請求被保護(hù)的接口保證是合法的(已經(jīng)認(rèn)證過的用戶) 接口可以從請求頭中獲取當(dāng)前用戶信息 每個(gè)...

    xiangchaobin 評論0 收藏0
  • 記一次翻譯站經(jīng)歷

    摘要:做這個(gè)記錄之前,剛完成使用作為公司前端項(xiàng)目的持續(xù)交付工具的實(shí)踐,打算寫的教程前先把官方文檔扒下來做個(gè)翻譯站。在實(shí)踐一番后,卡在不能頻密調(diào)取翻譯這塊上,項(xiàng)目無法進(jìn)行下去。 做這個(gè)記錄之前,剛完成使用drone作為公司前端項(xiàng)目的持續(xù)交付工具的實(shí)踐,打算寫的教程前先把官方文檔扒下來做個(gè)翻譯站。在實(shí)踐一番后,卡在不能頻密調(diào)取google翻譯這塊上,項(xiàng)目無法進(jìn)行下去。最后覺得經(jīng)歷的過程涉及的內(nèi)容...

    seasonley 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<