jwt原理及简单实现

本文探讨了cookies和session的不足,如安全性问题、跨域限制和性能影响,并介绍了JWT的优势,如减轻服务器压力、解决跨域问题。同时,阐述了JWT的构成(head、payload、sign)和验证过程,提供了基础的JWT生成与解析的Node.js代码示例,展示了如何在后台服务器部分实现JWT的验证功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

cookies、session和jwt的一些优缺点

先来说说cookies和session实际应用中的一些不足

  • 【缺点】cookies的安全性不好,攻击者可以通过获取本地cookies进行欺骗或者利用cookies进行CSRF攻击。
  • 【缺点】使用cookies时,在多个域名下,会存在跨域问题。
  • 【缺点】session在一定的时间里,需要存放在服务端,因此当拥有大量用户时,也会大幅度降低服务端的性能。
  • 【缺点】当有多台机器时,如何共享session也会是一个问题,也就是说,用户第一个访问的时候是服务器A,而第二个请求被转发给了服务器B,那服务器B如何得知其状态。

jwt

  • 【优点】使用JWT不需要后端进行记录,减轻服务器压力,每个token都是独立的。
  • 【优点】jwt不存在跨域问题,后台生成token返回到前台,前台保存到本地,每次请求在请求头携带token给后台进行验证即可。
  • 【缺点】token生成和验证都需要后台计算,而且由于每次向服务器发起请求都要携带token,太大了会造成请求缓慢。
  • 【中性】session比JWT好的地方在于在请求时浏览器会自动带http头部带上cookie,并且在用户持续使用时会不断地刷新session的过期时间,当浏览器关闭时自动清除session。相比之下JWT本身没法做到随着用户的使用而更新或手动清除,只能等自动过期

这里主要参考文章1  文章2

 

jwt实现

分析

编码:一个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生成函数

函数参数设计:

  • 第一个参数是自定义的一些要存储的信息,比如 {username: 'dsa'}
  • 第二个参数是秘钥(这是最重要的,不可泄露)
  • 第三个参数是一些其他选项,比如token最大时效(必填的){maxAge: 1 * 60 * 1000}
/**
 * @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测试。

先来登录获取token

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请求

前台访问 /home 进行token验证

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验证通过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值