JWT功能开发-后端生成token

JWT 基本概念

生成 JWT Token

安装 jsonwebtoken

npm i -S jsonwebtoken

使用

const jwt = require('jsonwebtoken')
const { PRIVATE_KEY, JWT_EXPIRED } = require('../utils/constant')

login(username, password).then(user => {
    if (!user || user.length === 0) {
      new Result('登录失败').fail(res)
    } else {
      const token = jwt.sign(
        { username },
        PRIVATE_KEY,
        { expiresIn: JWT_EXPIRED }
      )
      new Result({ token }, '登录成功').success(res)
    }
})

这里需要定义 jwt 的私钥和过期时间,过期时间不宜过短,也不宜过长,课程里设置为 1 小时,实际业务中可根据场景来判断,通常建议不超过 24 小时,保密性要求高的业务可以设置为 1-2 小时:

module.exports = {
  // ...
  PRIVATE_KEY: 'admin_imooc_node_test_youbaobao_xyz',
  JWT_EXPIRED: 60 * 60, // token失效时间
}

前端再次请求,结果如下:

{
  "code":0,
  "msg":"登录成功",
  "data":{
    "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTc0NDk1NzA0LCJleHAiOjE1NzQ0OTkzMDR9.9lnxdTn1MmMbKsPvhvRHDRIufbMcUD437CWjnoJsmfo"
  }
}

我们可以将该 token 在 jwt.io 网站上进行验证,可以得到如下结果:

{
  "username": "admin",
  "iat": 1574495704,
  "exp": 1574499304
}

可以看到 username 被正确解析,说明 token 生成成功

前端登录请求改造

修改 src/utils/request.js

service.interceptors.response.use(
  response => {
    const res = response.data

    if (res.code !== 0) {
      Message({
        message: res.msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      // 判断 token 失效的场景
      if (res.code === -2) {
        // 如果 token 失效,则弹出确认对话框,用户点击后,清空 token 并返回登录页面
        MessageBox.confirm('Token 失效,请重新登录', '确认退出登录', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.msg || '请求失败'))
    } else {
      return res
    }
  },
  error => {
    let message = error.message || '请求失败'
    if (error.response && error.response.data) {
      const { data } = error.response
      message = data.msg
    }
    Message({
      message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

JWT 认证

安装 express-jwt

npm i -S express-jwt

创建 /router/jwt.js

const expressJwt = require('express-jwt');
const { PRIVATE_KEY } = require('../utils/constant');

const jwtAuth = expressJwt({
  secret: PRIVATE_KEY,
  credentialsRequired: true // 设置为false就不进行校验了,游客也可以访问
}).unless({
  path: [
    '/',
    '/user/login'
  ], // 设置 jwt 认证白名单
});

module.exports = jwtAuth;

/router/index.js 中使用中间件

const jwtAuth = require('./jwt')

// 注册路由
const router = express.Router()

// 对所有路由进行 jwt 认证
router.use(jwtAuth)

/utils/contants.js 中添加:

module.exports = {
  // ...
  CODE_TOKEN_EXPIRED: -2
}

修改 /model/Result.js

expired(res) {
  this.code = CODE_TOKEN_EXPIRED
  this.json(res)
}

修改自定义异常:

router.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    new Result(null, 'token失效', {
      error: err.status,
      errorMsg: err.name
    }).expired(res.status(err.status))
  } else {
    const msg = (err && err.message) || '系统错误'
    const statusCode = (err.output && err.output.statusCode) || 500;
    const errorMsg = (err.output && err.output.payload && err.output.payload.error) || err.message
    new Result(null, msg, {
      error: statusCode,
      errorMsg
    }).fail(res.status(statusCode))
  }
})

前端传入 JWT Token

后端添加路由的 jwt 认证后,再次请求 /user/info 将抛出 401 错误,这是由于前端未传递合理的 Token 导致,下面我们就修改 /utils/request.js,使得前端请求时可以传递 Token:

service.interceptors.request.use(
  config => {
    // 如果存在 token 则附带在 http header 中
    if (store.getters.token) {
      config.headers['Authorization'] = `Bearer ${getToken()}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

前端去掉 /user/info 请求时传入的 token,因为我们已经从 token 中传入,修改 src/api/user.js

export function getInfo() {
  return request({
    url: '/user/info',
    method: 'get'
  })
}

用户查询 /user/info API

/db/index.js 中添加:

function queryOne(sql) {
  return new Promise((resolve, reject) => {
    querySql(sql)
      .then(results => {
        if (results && results.length > 0) {
          resolve(results[0])
        } else {
          resolve(null)
        }
      })
      .catch(error => {
        reject(error)
      })
  })
}

/services/user.js 中添加:

function findUser(username) {
  const sql = `select * from admin_user where username='${username}'`
  return queryOne(sql)
}

此时有个问题,前端仅在 Http Header 中传入了 Token,如果通过 Token 获取 username 呢?这里就需要通过对 JWT Token 进行解析了,在 /utils/index.js 中添加 decode 方法:

const jwt = require('jsonwebtoken')
const { PRIVATE_KEY } = require('./constant')

function decode(req) {
  const authorization = req.get('Authorization')
  let token = ''
  if (authorization.indexOf('Bearer') >= 0) {
    token = authorization.replace('Bearer ', '')
  } else {
    token = authorization
  }
  return jwt.verify(token, PRIVATE_KEY)
}

修改 /router/user.js

router.get('/info', function(req, res) {
  const decoded = decode(req)
  if (decoded && decoded.username) {
    findUser(decoded.username).then(user => {
      if (user) {
        user.roles = [user.role]
        new Result(user, '获取用户信息成功').success(res)
      } else {
        new Result('获取用户信息失败').fail(res)
      }
    })
  } else {
    new Result('用户信息解析失败').fail(res)
  }
})

此时在前端重新登录,登录终于成功了!

修改 Logout 方法

修改 src/store/modules/user.js

logout({ commit, state, dispatch }) {
    return new Promise((resolve, reject) => {
      try {
        commit('SET_TOKEN', '')
        commit('SET_ROLES', [])
        removeToken()
        resetRouter()
        // reset visited views and cached views
        // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
        dispatch('tagsView/delAllViews', null, { root: true })
        resolve()
      } catch (e) {
        reject(e)
      }
    })
}

关于 RefreshToken

如果你的场景需要授权给第三方 app,那么通常我们需要再增加一个 RefreshToken 的 API,该 API 的用途是根据现有的 Token 获取用户名,然后生成一个新的 Token,这样做的目的是为了防止 Token 失效后退出登录,所以 app 一般会在打开时刷新一次 Token,该 API 的实现方法比较简单,所需的技术之前都已经介绍过,大家可以参考之前的文档进行实现

你可能感兴趣的:(JWT功能开发-后端生成token)