tips
:接下去會在github寫博客,簡書不再更新和修改文章,歡迎大家逛逛我的新博客點擊查看 ,我會盡量用更容易理解的方式寫好每一篇博客,大家一起學習交流??。
前一個項目用的是cookie+session,由于想試試新花樣,現在這個項目用的是Json web token。也做一下總結。
講Json web token 之前先來了解下什么是token,因為jwt本質就是一個token
什么token
token的意思是“令牌”,是用戶身份的驗證方式,最簡單的token組成:uid(用戶唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,由token的前幾位+鹽以哈希算法壓縮成一定長的十六進制字符串,可以防止惡意第三方拼接token請求服務器)。還可以把不變的參數也放進token,避免多次查庫
什么是JSON Web Token
JSON web Token,簡稱JWT,本質是一個token,是一種緊湊的URL安全方法,用于在網絡通信的雙方之間傳遞。一般放在HTTP的headers 參數里面的authorization里面,值的前面加Bearer關鍵字和空格。除此之外,也可以在url和request body中傳遞。
JSON Web Token的組成
一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名依順序用點號(".")鏈接而成:1.header,2.payload,3.signature。
頭部(Header)里面說明類型和使用的算法,比如:
{
"alg": "HS256",
"typ": "JWT"
}
說明是JWT(JSON web token)類型,使用了HMAC SHA 算法。然后將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
載荷(Payload)載荷就是存放有效信息的地方,含三個部分:
1.標準中注冊的聲明,2.公共的聲明,3.私有的聲明
1.標準中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
2.公共的聲明 :
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
3.私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
定義一個payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后將其進行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
把場景2的操作描述成一個json對象。其中添加了一些其他的信息,幫助今后收到這個JWT的服務器理解這個JWT。 當然,你還可以往載荷放非敏感的用戶信息,比如uid
signature
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');//TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意
secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。
將這三部分用.連接成一個完整的字符串,構成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
應用場景:
1.瀏覽器將用戶名和密碼以post請求的方式發送給服務器。
2.服務器接受后驗證通過,用一個密鑰生成一個JWT。
3.服務器將這個生成的JWT返回給瀏覽器。
4.瀏覽器存儲JWT并在使用時將JWT包含在authorization header里面,然后發送請求給服務器。
5.服務器可以在JWT中提取用戶相關信息。進行驗證。
6.服務器驗證完成后,發送響應結果給瀏覽器。
jwt 相比 session 的有點在哪
- 能解決csrf淺談CSRF攻擊方式
- 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
- 因為有了payload部分,所以JWT可以在自身存儲一些其他業務邏輯所必要的非敏感信息。
- 便于傳輸,jwt的構成非常簡單,字節占用很小,所以它是非常便于傳輸的。
- 它不需要在服務端保存會話信息, 所以它易于應用的擴展
安全相關
- 不應該在jwt的payload部分存放敏感信息,因為該部分是客戶端可解密的部分。
- 保護好secret私鑰,該私鑰非常重要。
- 建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
代碼實例
客戶端 這里引用axios
axios.interceptors.request.use(
config => {
const token = localStorage.getItem('userToken');
if (token) {
config.headers.common['Authorization'] = 'Bearer ' + token;
}
return config;
},
error => {
return Promise.reject(error);
}
);
后端(koa) 這里引用jsonwebtoken 這個npm包
登錄時生成一個token
const jwt = require("jsonwebtoken");
const secret = require("../config").secret;
const token = jwt.sign(payload, secret, {
expiresIn: Math.floor(Date.now() / 1000) + 24 * 60 * 60 // 一天
});
寫一個jwt驗證的中間件
/**
* @file 處理jwt驗證的中間件
*/
const jwt = require("jsonwebtoken");
const secret = require("../config").secret;
module.exports = async function(ctx, next) {
// 同步驗證
const auth = ctx.get('Authorization')
const token = auth.split(' ')[1];
try {
//解碼取出之前存在payload的user_id 和 name
const payload = jwt.verify(token, secret)
ctx.user_id = payload.id;
ctx.name = payload.name;
await next()
} catch (err) {
ctx.throw(401, err)
}
}
在路由(koa-router)這邊使用這個中間件
const verify = require('../middlewares/verify');
router.post('/register', register) //注冊
.post('/login', login) //登錄
.get('/message', verify, message) // 獲取首頁列表信息
其他
還有一個 oauth token :一個關于授權(authorization)的開放網絡標準協議。
有一個"云沖印"的網站,可以將用戶儲存在Google的照片,沖印出來。用戶為了使用該服務,必須讓"云沖印"讀取自己儲存在Google上的照片。
具體看這個 理解OAuth 2.0