JWT_nodeJs_express_vue 实例

本文详细介绍了如何在Node.js后端与Vue前端项目中实现JWT身份验证流程,包括token的生成、存储、验证及刷新机制。

JWT 的定义和组成部分就不多说了,网上有很多,我的理解就是一个有规范内容的 token。这里记录一下我项目中使用到的 JWT 的经历。

nodejs 部分:

在 nodejs 中我使用的是 jsonwebtoken 这个第三方依赖,它对 JWT 的解密,加密,校验都已经封装好了。

https://github.com/auth0/node-jsonwebtoken

  1. 首先是登录验证时的签发 token 部分:
// ...
// 生成 token
let token = jwt.sign({
    userId: data.rows[0].userId,
    roleId: data.rows[0].roleId
}, tokenConfig.secret, {
    expiresIn: 60 * 60 * 24 * 3// 授权时效 3 天
})
showRes.showData(res, {
    token: token,
    userInfo: {
        userId: data.rows[0].userId,
        userName: data.rows[0].userName,
        roleId: data.rows[0].roleId
    }
}, '请接收 token')
// ...

(tokenConfig.secret 是一个配置文件存储了密钥)jwt 可以在 token 中放入用户的 id 和用户的权限,jwt.sigh 签发加密,可以单向加密,也可以非对称加密。我这里是默认的,token 准备好后发送给前端。

  1. nodejs 我使用的是 express 框架,需要挂载一个中间件用来校验 token(因为之后的前端发送 ajax 请求后来都会在 http 的头部带上 token 以供后端校验):
let express = require('express')
let app = express.Router()
let jwt = require('jsonwebtoken');
var tokenConfig = require('../common/token-config')

// 校验 token 中间件
app.use(function (req, res, next) {
  // 登录接口不检测
  if (req.originalUrl != '/api/v1/validate') {
    // 拿取 token 数据 
    let token = req.body.token || req.query.token || req.headers['authorization'];
    if (token) {
      // 解码 token (验证 secret 和检查有效期(exp))
      jwt.verify(token, tokenConfig.secret, function (err, decoded) {
        if (err) {
          return res.status(401).send({ code: 0, msg: '无效的token' });
        } else {
          // 继续下一步路由
          return next();
        }
      });
    } else {
      // 没有拿到token 返回错误 
      return res.status(401).send({
        code: 0,
        msg: '没有找到token'
      });
    }
  } else {
    return next();
  }
});

验证通过的话就放行,不通过的话就返回 http 状态给前端,我这里用的是 401。

前端部分:

前端使用的是 vue-cli 搭出来的目录结构,使用了 axios。

  1. 前端首先要把后端返回过来的 token 存放好,我选择存放在 localStorage 中,login.vue:
// ...
localStorage.setItem("NR_JWT_TOKEN", res.data.data.token)
// 在 vuex 中存入用户信息
this.$store.commit({
    type: 'setUserInfo',
    data: res.data.data.userInfo
})
this.$message.success('验证成功~');
// ...

这里我有一步将用户基本信息存入到 vuex 的步骤,目的是让各处的组件都能方便去获取到,用户的基本信息是后端登录验证签发 token 的时候一起发过来的。

  1. 在 vuex 中:
import { getUserInfoByToken } from '@/api/api'

const state = {
    userInfo: sessionStorage.getItem('NR-USER-INFO') && sessionStorage.getItem('NR-USER-INFO') != 'undefined' ? JSON.parse(sessionStorage.getItem('NR-USER-INFO')) : null
}

const mutations = {
    setUserInfo(state, playload) {
        state.userInfo = playload.data
        sessionStorage.setItem('NR-USER-INFO', JSON.stringify(playload.data))
    }
}

const getters = {
}
const actions = {
    getUserInfoByToken: context => {
        getUserInfoByToken({}).then((res) => {
            if (res.data.code === 1) {
                context.commit({
                    type: 'setUserInfo',
                    data: res.data.data
                })
                return new Promise((resolve, reject) => {
                    resolve()
                })
            } else {
                // 返回一个 Promise 告知获取用户信息失败需要回到登录页
                return new Promise((resolve, reject) => {
                    reject()
                })
            }
        })
    }
}
export default {
    state,
    getters,
    actions,
    mutations
}

这里除了将信息保存在 vuex 里面,还存了一份在 sessionStorage 里面,因为,vuex 虽然用起来方便,但是页面一刷新就没有了,sessionStorage 是页面刷新都存在,但是页面一关就没有了(关页面不怕,因为 token 已经存在 localStorage 里面了,到时候再拿了去查信息就行,我这里的 actions 里面的 getUserInfoByToken 方法就是做这个用的)。在 state.userInfo 属性里和 sessionStorage 绑上,页面一刷新,vuex 中信息丢失,立即会去 sessionStorage 里拿。

  1. 专门新建了一个 api.js 用来管理 axios 的请求以及 axios 拦截器的设置:
import axios from 'axios'
const Qs = require('qs')
// 为了错误码 401 时路由跳转到登录页面
import router from '../router'
import { MessageBox } from 'element-ui';

console.log(process.env.NODE_ENV)

const base = '/api/v1'

// axios request 拦截器
axios.interceptors.request.use(
  config => {
    if (localStorage.getItem('NR_JWT_TOKEN')) {
      config.headers.Authorization = localStorage.getItem('NR_JWT_TOKEN')
    }
    return config
  },
  error => {
    return Promise.reject(error);
  }
)

// axios response 拦截器
axios.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          MessageBox.alert('您的验证信息已失效,请重新登录~', '提示', {
            confirmButtonText: '确定',
            callback: action => {
              // 清除 token 信息
              localStorage.removeItem("NR_JWT_TOKEN");
              sessionStorage.removeItem("NR-USER-INFO");
              router.replace({
                path: 'login',
                query: { redirect: router.currentRoute.fullPath }
              });
            }
          });
          break;
      }
    }
  }
)

// get,post,put,delete按照这个套路写
// export const GetSpecialty = params => { return axios.get(`${base}/GetSpecialty`, { params: params }) }
// export const Register = params => { return axios.post(`${base}/Register`, Qs.stringify(params)) }
// export const AddMenu = params => { return axios.put(`${base}/menu`, Qs.stringify(params)) }
// export const DeleteMenu = params => { return axios.delete(`${base}/menu`, { params: params }) }

// 测试 api
export const TestApi = params => { return axios.get(`${base}/test`, { params: params }) }

// 用户表
export const userValidate = params => { return axios.post(`${base}/validate`, Qs.stringify(params)) }
export const getUserInfoByToken = params => { return axios.post(`${base}/getUserInfoByToken`, Qs.stringify(params)) }

axios 这里写了两个拦截器,一个是 http 请求的时候,一个是响应的时候,请求的时候在 header 里面附带着。接收响应的时候如果是 401 说明失效了,清空 localStorage,sessionStorage 并转至登录页面。

  1. 下面是 vue-router 路由里面的配置:
// 登录,权限验证
router.beforeEach((to, from, next) => {
  if (localStorage.getItem('NR_JWT_TOKEN')) {
    /* vuex 中用户信息不存在,说明页面关过,目前无需登录,但需要携带 token 查询一下用户信息
      (利用 token 中的 userId,顺便验证 token 是否过期)*/
    if (!store.state.user.userInfo) {
      // 调用 vuex 中的 getUserInfoByToken action
      store.dispatch('getUserInfoByToken').catch(() => {
        ElementUI.MessageBox.alert('您的验证信息已失效,请重新登录~', '提示', {
          confirmButtonText: '确定',
          callback: action => {
            // 清除 token 信息
            localStorage.removeItem("NR_JWT_TOKEN");
            sessionStorage.removeItem("NR-USER-INFO");
            router.replace({
              path: 'login',
              query: { redirect: router.currentRoute.fullPath }
            });
          }
        });
        return
      })
    }
    if (to.path === '/login') {
      // token 存在前往 login 页不用再登录转到首页
      next('/')
    } else {
      next()
    }
  } else {
    sessionStorage.removeItem("NR-USER-INFO");    
    if (to.path === '/login') {
      // token 不存在前往 login 页放行      
      next()
    } else {
      next({ path: '/login' })
    }
  }
})

这里的逻辑是首先判断 LS 中 token 在不在,不在直接再次登录。如果存在,再看 VUEX 中信息有没有,如果没有的话,说明页面关过,目前无需登录,但需要携带 token 查询一下用户信息 (利用 token 中的 userId,顺便验证 token 是否过期,用的是 vuex 中 actions 里面的 getUserInfoByToken 方法)。

大概的流程和需要用的地方就是这些,请大家多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值