jwt

作者: 回不去的那些时光 | 来源:发表于2020-03-04 13:16 被阅读0次

    jwt简介

    JSON Web Token(JWT)是非常流行的跨域身份验证解决方案。

    jwt构成

    HEADER: ALGORITHM & TOKEN TYPE

    生成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失效时间
    }
    

    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)
      }
    })
    

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

    关于 RefreshToken

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

    相关文章

      网友评论

          本文标题:jwt

          本文链接:https://www.haomeiwen.com/subject/zyxvlhtx.html