NodeJS实现JWT原理

1.会话管理

我们用nodejs为前端或者其他服务提供resful接口时,http协议他是一个无状态的协议,有时候我们需要根据这个请求的上下文获取具体的用户是否有权限,针对用户的上下文进行操作。所以出现了cookies session还有jwt这几种技术的出现, 都是对HTTP协议的一个补充。使得我们可以用HTTP协议+状态管理构建一个的面向用户的WEB应用。

2.session和cookies

个人理解,cookie是靠session_id完成服务端与客户端的通信的,当我们第一次登录时,服务端会去寻找我们的登录信息,然后将登录信息回写到cookie中,而session_id就是我们寻找登录信息的一个标识。

有几点需要注意一下:
1.cookie数据存放在客户的浏览器上,session数据放在服务器上。
2.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3.session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4.单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制。
5.cookie在多个站点下会存在跨域问题。

3.JWT定义

JWT全称(json web token),就是我们开发中常用到的Token。

4.JWT原理

既然Token是对Http协议的一个补充,可以说代替Cookie,那么Token也是要存储用户信息的,而在cookie中都是以键值对形式存储的:比如(name=‘lgh’; )一定是以分号加一个空格结尾,而Token一般是服务端返回给客户端的一个JSON,但是我们与服务端通信的时候,往往这个JSON会加上签名(也就是一串你不认识的英文,服务端认识)。

5.JWT认证流程

浏览器发起请求登陆,携带用户名和密码;
服务端根据用户名和明码到数据库验证身份,根据算法,将用户标识符打包生成 token,
服务器返回JWT信息给浏览器,JWT不应该包含敏感信息,这里有一个加密过程,这是很重要的一点。
浏览器发起请求获取用户资料,把刚刚拿到的 Token一起发送给服务器,一般放在header里面。
服务器发现数据中有 Token,服务端对Token解密,然后去对比,验证通过。
服务器返回该用户的用户资料。
服务器可以在payload设置过期时间, 如果过期了,可以让客户端重新发起验证。

6.代码实现(Vue+NodeJS+MySql)

场景:比如我现在正在做的一个商城项目,一般首页,产品页,给客户看得界面,都是不需要身份校验的,我们逛淘宝京东,就算没有登录我们也能浏览商品,但是当我们点购买,点击购物车一些入口时,这时候是需要身份校验的,你必须登录了才能看自己购物车信息,才能购买商品。还有一点,cookie存储在客户端,我们会话窗口一关,cookie就没了,然后我们再去点购物车,服务端肯定需要我们再次登录。Token就是解决了这个问题。

1.连接数据库
这里我就简单写了两张表,一张user,一张cart
2.搭建服务器
app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors=require("cors");
// 引入路由模块(用户)
const user=require("./routes/users");
/*引入token的模块*/
const jwt=require("./jwt.js")
const app = express();
app.listen(8080);
app.use(cors({
  origin:['http://localhost:8081'],
  credentials:true
}));
3.连接池
pool.js
//创建mysql连接池
const mysql = require('mysql');
const pool = mysql.createPool({
  host: '127.0.0.1',
  user: 'root',
  password: '',
  database: 'user',
  connectionLimit: 10 
});
//把创建好的连接池导出
module.exports = pool;
4.JWT模块(Token)
进入路由模块之前,需要用到一个签名工具。(私钥加密、公钥解密)
这里我也写好了
// 引入模块依赖
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
//生成token
function generateToken(data){
	// 随机生成一个时间戳
    let created = Math.floor(Date.now() / 1000);
    // 读取硬盘上的私钥文件,利用jwt对其签名,这里会生成token
    let cert = fs.readFileSync(path.join(__dirname, './pem/rsa_private_key.pem'));//私钥 可以自己生成
    let token = jwt.sign({
        data, // 需要加密的用户信息
        exp: created + 60 * 60,// 设置token过期时间
    }, cert, {algorithm: 'RS256'}); // 这是一个签名算法,这里不过多介绍
    return token;
}
// 校验token
function verifyToken(token) {
    let cert = fs.readFileSync(path.join(__dirname, './pem/rsa_public_key.pem'));//公钥 可以自己生成
    let res;
    try {
        if(token!==undefined){
            let result = jwt.verify(token, cert, {algorithms: ['RS256']}) || {};
            res = result.data || {};
        }
    } catch (e) {
        res = e;
    }
    return res;
}

module.exports = { generateToken, verifyToken };

加密解密模块就完成了,我们需要把方法暴露出来用module.exports方法

5.路由模块(接口)
user.js
const express=require("express");
const router=express.Router();
const pool=require("../pool");
const jwt=require("../jwt.js")
// 登录接口
router.post("/islogin",(req,res)=>{
  var {uname,upwd,remember}=req.body;
  var sql="select * from user_user where uname=? and binary upwd=?";
  pool.query(sql,[uname,upwd],(err,result)=>{
    err&&console.log(err);
    if(result.length>0){
      res.write(JSON.stringify({
        ok:1,uname:result[0]['uname'],
        remember:remember||false,
        token:jwt.generateToken(result[0])
      }));
    }else{
      res.write(JSON.stringify({ok:0,msg:"用户名或密码错误!"}));
    }
    res.end();
  })
})
// 购物车接口
router.get("/orders",(req,res)=>{
  var aid=req.user.uid
  if(aid==null){
    res.write(JSON.stringify({ok:0}));
    res.end();
  }else{
    var sql="select * from user_order where aid=?";
    pool.query(sql,[aid],(err,result)=>{
      res.write(JSON.stringify({ok:1,data:result[0]}));
      res.end();
    })
  }
})
module.exports=router;
6.Vue模块
在Vue里面我们需要考虑两个东西,
第一个是界面(简单的做了一下)
第二个是Axios
因为在访问个人,购物车等入口时,我们需要在每次请求中带上Token向后端发请求,每次都写Token太麻烦了,所以这里就用到了Axios拦截器。
axios.js
这里存在一个逻辑问题,当用户第一次登陆才需要校验,
登陆成功之后,服务端返回一个Token
if(result.length>0){
      res.write(JSON.stringify({
        ok:1,uname:result[0]['uname'],
        remember:remember||false,
        token:jwt.generateToken(result[0])
      }));
用户登陆成功,服务端返回一个Token,为了保持登陆状态可以看缓存中有没有Token,
1.登陆成功,缓存中存一个token以及Vuex中存储用户信息
2.页面关闭怎么办,发起请求时先去缓存中找Token,然后带在请求头中,如果找不到Token,发起请求时,服务端校验。
校验过程如下:用Node中间件,在还没进入到路由之前做了一个拦截。
3.如果校验失败,一定要从缓存中移除Token,知道下一次登录成功再存进去
也就是app.use('/user',user)之前
app.use((req, res, next)=>{ 
  if (req.url != '/user/islogin' && (req.url.startsWith("/user") || req.url.startsWith("/orders"))) {
    let token = req.headers.token;
    let result = jwt.verifyToken(token);
    // 如果考验通过就next,否则就返回登陆信息不正确
    console.log(result)
    if(result === undefined){
      res.send({status:403, msg:"未提供证书"})
    }else if (result.name == 'TokenExpiredError') {
      res.send({status: 403, msg: '登录超时,请重新登录'});
    } else if (result.name=="JsonWebTokenError"){
      res.send({status: 403, msg: '证书出错'})
    } else{
      req.user=result;
      next();
    }
  } else {
    next();
  }
const Axios=axios.create({
  baseURL:"http://localhost:8080",
  withCredentials:true
})
Axios.interceptors.request.use(
  config=>{
  // post请求不能直接发对象参数,这里用qs模块转一下
    if(config.method ==="post"){
      config.data=qs.stringify(config.data)
    }
    if(localStorage.getItem("token")){
      config.headers.token=localStorage.getItem("token");
    }
    if(sessionStorage.getItem("token")){
      config.headers.token=sessionStorage.getItem("token");
    }
    return config;
  },
  error=>{
    console.log(error);
    Promise.reject(error);
  }
);
Axios.interceptors.response.use(
  res=>{
    if(res.data.status==403){
      localStorage.removeItem("token");
      sessionStorage.removeItem("token");
      console.log(403)
    }else if(res.data.ok==-1){
      alert(res.data.msg+" 请先登录 !");
      console.log(-1)
    }else if(res.data.token){
      console.log(res.data.token)
      if(res.remember==="true"){
        localStorage.setItem("token",res.data.token);
      }else{
        sessionStorage.setItem("token",res.data.token);
      }
    }
    return res;
  },
  error=>{
    
  }
)
export default {
  install: function(Vue, Option){
    Vue.prototype.$axios=Axios;
  }
}

效果图如下:
1.登录
在这里插入图片描述

登录成功后返回一个Token
在这里插入图片描述
2.访问购物车
在右侧看到请求头中带了一个Token,这里验证是通过了的。(代码都是从自己项目里面copy的,很久之前自己练手写的一个demo)
在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值