这里主要参考文章1 文章2
编码:一个token是一串base64字符,大概分成head、payload、sign三部分,这三部分以 . 分割。其中head记录的是加密算法,payload记录的是你定义的一些信息,sign则是head(base64字符) + payload(base64字符) + 秘钥的加密base64字符。
解码:在payload保存过期时间,解码时判断是否过期。head(base64字符) + payload(base64字符) + 秘钥再加密看是否等于sign,不等于则被改动过。
首先准备两个转换base64字符的函数
// 普通字符转base64字符
function toBase64(str){
return new Buffer(str).toString('base64');
}
// base64字符转普通字符
function fromBase64(str){
return new Buffer(str, 'base64').toString();
}
签名加密函数
const crypto = require('crypto');
/**
* @param {String} str 要加密的字符串
* @param {String} jwtSecretKey 秘钥
* @method 签名
*/
function sign(str, jwtSecretKey){
return crypto.createHmac('sha256', jwtSecretKey).update(str).digest('base64');
}
token生成函数
函数参数设计:
/**
* @param {Object} payload 自定义信息
* @param {String} jwtSecretKey 秘钥
* @param {Object} options 其他选项
* @method 生成token
*/
function encode(payload, jwtSecretKey, options){
// token头部,加密算法
let headPart = toBase64(JSON.stringify({"alg": "sha256","typ": "JWT"}));
// token信息内容部分,自定义的信息
payload.iat = Date.now(); //token生成时间
payload.exp = Date.now() + options.maxAge; //token过期时间
let payloadPart = toBase64(JSON.stringify(payload));
// token签名部分
let signPart = sign(`${headPart}.${payloadPart}`, jwtSecretKey);
return `${headPart}.${payloadPart}.${signPart}`;
}
解析token函数
/**
* @param {String} token 前台传来的token
* @param {String} jwtSecretKey 秘钥
* @method 解析token
* @return {Boolean} 返回布尔值
*/
function decode(token, jwtSecretKey){
// 通常前台传过来的token字符串前面都是加上'Bearer '
let [headPart, payloadPart, signPart] = token.split(' ')[1].split('.');
let payload = JSON.parse(fromBase64(payloadPart));
// 如果token过期了,return false
if(Date.now() > payload.exp) return false;
// 如果token被改过,return false
if(sign(`${headPart}.${payloadPart}`, jwtSecretKey) !== signPart) return false;
return true;
}
这里我就不使用任何框架了,直接用Node.js原生撸,前台则使用postman测试。
const http = require('http');
const url = require('url');
const jwt = require('./jwt');
// jwt秘钥
const jwtSecretKey = 'lala';
http.createServer((req, res) => {
let {pathname} = url.parse(req.url);
// 前台登录
if(pathname === '/login'){
let chunkArr = [];
req.on('data', chunk => {
chunkArr.push(chunk);
})
req.on('end', () => {
let data = JSON.parse(Buffer.concat(chunkArr).toString());
// 如果登录成功,就给前台返回一个token
if(data.username === 'dsa' && data.pwd === '321'){
// 生成token
let token = jwt.encode({
username: 'dsa'
}, jwtSecretKey, {
maxAge: 1*60*1000 //最大时效1分钟
});
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
code: 0,
msg: '登录成功',
data: {
token
}
}))
}
})
}
}).listen(8888, () => {
console.log('server listening on port 8888');
})
用postman请求
const http = require('http');
const url = require('url');
const jwt = require('./jwt');
// jwt秘钥
const jwtSecretKey = 'lala';
http.createServer((req, res) => {
let {pathname} = url.parse(req.url);
// 前台登录
if(pathname === '/login'){
let chunkArr = [];
req.on('data', chunk => {
chunkArr.push(chunk);
})
req.on('end', () => {
let data = JSON.parse(Buffer.concat(chunkArr).toString());
// 如果登录成功,就给前台返回一个token
if(data.username === 'dsa' && data.pwd === '321'){
// 生成token
let token = jwt.encode({
username: 'dsa'
}, jwtSecretKey, {
maxAge: 1*60*1000 //最大时效1分钟
});
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
code: 0,
msg: '登录成功',
data: {
token
}
}))
}
})
}else if(pathname === '/home'){ //访问home需要验证token
let token = req.headers.authorization;
console.log(typeof token, token);
// 如果没有token或者token验证错误
if(!token || !jwt.decode(token, jwtSecretKey)){
res.statusCode = 401;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
code: 1,
msg: 'No authority'
}))
}else{
res.end('Hello World');
}
}
}).listen(8888, () => {
console.log('server listening on port 8888');
})
如果token验证错误
如果token验证通过