Node.js 高级篇(四):实现 token 身份验证

1. 生成私钥和公钥

# 打开 openssl
> openssl

# 生成私钥:
OpenSSL > genrsa -out rsa_private_key.pem 2048

# 根据私钥生成公钥:
OpenSSL > rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

在这里插入图片描述


2. 使用 jsonwebtoken 注册和验证 token

jsonwebtoken 文档

utils/token.js

const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

// 创建Token
const signToken = (user) => {
    
    
  // 私钥
  const privateKey = fs.readFileSync(path.join(__dirname, '../secretKeys/rsa_private_key.pem'))
  const params = {
    
    
    ...user,
    _id: user._id,
  }
  // 将登陆的用户信息生成token,使用私钥进行非对称加密,过期时间为 2h
  return jwt.sign(params, privateKey, {
    
     algorithm: 'RS256', expiresIn: '2h' })
}

// 校验Token
const verifyToken = (token) => {
    
    
  // 公钥
  const publicKey = fs.readFileSync(path.join(__dirname, '../secretKeys/rsa_public_key.pem'))
  try {
    
    
    // 根据公钥验证token
    // 验证成功则返回注册token的原始信息
    return jwt.verify(token, publicKey)
  } catch (err) {
    
    
    // 验证失败则返回err
    // 处理非法token、过期token等问题
    return null
  }
}

module.exports = {
    
    
  signToken,
  verifyToken,
}

3. 登录,注册token并返回给客户端

control/account.js

const usersModel = require('../model/usersManage')
const {
    
     getComparePwd } = require('../utils/getBcryptPwd')
const {
    
     signToken } = require('../utils/token')

// 用户登录
const login = async (req, res, next) => {
    
    
  res.set('Content-Type', 'application/json; charset=utf-8')
  const {
    
     username, password } = req.body
  const user = await usersModel.findOneUser(username)
  if (user) {
    
    
    // 验证密码是否正确
    const match = await getComparePwd(password, user.password)
    if (match) {
    
    
      // 生成token,并将token返回给前端
      const token = signToken(user)
      // res.set('X-Access-Token', token)
      res.render('success', {
    
    
        data: JSON.stringify({
    
     token })
      })
    } else {
    
    
      res.render('error', {
    
    
        data: JSON.stringify("用户名或密码错误")
      })
    }
  } else {
    
    
    res.render('error', {
    
    
      data: JSON.stringify("用户名或密码错误")
    })
  }
}

module.exports = {
    
    
  login,
}

4. auth中间件验证 token

middlewares/auth.js

const {
    
     verifyToken } = require('../utils/token')

const auth = (req, res, next) => {
    
    
  const verifyResult = verifyToken(req.get('x-token'))
  if (verifyResult) {
    
    
    // 将从token中获取的用户信息通过res.locals传给下一个中间件
    res.locals.currentUser = verifyResult;
    next()
  } else {
    
    
    res.render('notLogin', {
    
    
      data: JSON.stringify("请登录")
    })
  }
}

exports.auth = auth

routes/account.js

const express = require('express');
const router = express.Router();
const {
    
     login, info } = require('../control/accounts')
const {
    
     auth } = require('../middlewares/auth')

// 登录
router.post('/login', login);

// 用户信息
router.get('/info', auth, info);

module.exports = router;

5. 退出登录

JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。因此我们有以下几种方案来优化解决 JWT 无状态的问题。

方案:

5.1 黑名单校验

凡是退出登录的token都放入黑名单中,定期清理。

每次用户请求服务器都校验token是否在黑名单


5.2 版本号校验

访问时从token中取出版本号和用户id 和 redis中存储 用户id和版本号 做对比,不一致则不给访问。

用户登出的时候在redis中把用户版本号加一。


5.3 过期时间校验

登录时token附带创建时间。访问时校验redis存储的过期时间,如果创建时间大于过期时间则不给访问。


5.4 Token副本校验

redis中存储token副本,用户请求时候校验,如果redis中不存在该副本则不给通过。


5.5 无为而治

只让前端清理token,后端不理会。(大多数)


方案的选择

对于需要只有一人一终端登录的服务采用 【版本号校验】 解决方案

相当于乐观锁,但是是用redis来实现,速度快

还方便踢人下线


对于需要注销token的服务,采用 【Token副本校验 or 过期时间校验】解决方案

比查阅黑名单列表还要快得多

速度相当快


对于不需要注销token(大部分服务),则采用 【无为而治】解决方案

完美的JWT登录的使用方式

猜你喜欢

转载自blog.csdn.net/qq_41887214/article/details/123968801#comments_21672765