Nuxt.js 部署負載均衡后 通過redis緩存token鑒權


1.背景:

  項目前后台分離, 前端技術棧Nuxt.js + express.js 三台服務器 后端 5台服務器  做負載均衡處理

2.問題:

  后端不做用戶狀態緩存, 僅通過user_id + user_acc 等 做AES加密生成 token,請求響應解密token 是否正確,

  前端token如果只是本地緩存或狀態層做token的非空驗證,無法鑒別token是否偽造

3.解決思路:

  在node中間層做用戶token鑒權;

4.解決方法:

  4.1 寫入狀態: 用戶登錄 --> node中間層標記cookie(例: nd-token: tokenKey(uuid + timestamp等生成唯一string)) --> 后端生成 user-token 返回node層  --> node層寫入redis (key為 tokenKey, value 為后端返回 user-token )

  4.2 (中間層)鑒權: 用戶交互 --> node中間層讀取cookie : nd-token;

    4.2.1 若 nd-token 為空,  判斷為游客狀態;

    4.2.2 若 nd-token 存在, 獲取 tokenKey , 讀取 redis中, tokenKey的value: user-token   --> 攜帶token 請求后端 ( 這里可以根據前后約定, user-token 放header 或 body )

5. 后續處理:

  a. 4.1 步驟中: 寫入 redis 的時候, expire; (redis過期銷毀, 對應cookie 可以設置也可以不設置, 若cookie獲取到, 查詢redis 查詢不到 即登錄狀態過期)

  b. 退出登錄狀態:  用戶退出登錄 --> node中間層 讀取cookie : nd-token , redis 刪除對應 tokenKey; --> 后端退出登錄 --> 返回退出成功

 

6. 代碼部分

  6.1 業務代碼:

redis.js  // 這里我司是部署阿里雲內網,全是走內部通信

const redis = require('redis')
// const host = '172.XXX.XXX.182' // 內網
const host = '47.XXX.XXX.165' // 外網
// const host = '127.0.0.1'
const port = 6379
const redisClient = redis.createClient({
  host,
  port,
  db: 3
})

redisClient.on('error', err => {
  console.log(err)
})

module.exports = redisClient

 

server/index.js

const session = require('express-session')
const redisClient = require('../redis')
const RedisStore = require('connect-redis')(session)

const sessionStore = new RedisStore({
  client: redisClient
})

// session配置
app.use(
  session({
    secret: 'super-secret-key',
    cookie: {
      maxAge: 60 * 1000
    },
    resave: false,
    saveUninitialized: false,
    rolling: true,
    store: sessionStore // 存儲在redis中
  })
)
// 登錄重寫
app.post('/re-api/login', function(req, res) {
  const cookie = req.headers.cookie
  request(
    {
      headers: {
        cookie,
        Accept: 'application/json, text/plain, */*'
      },
      url: env.API_URL + '/user/user-login',
      method: 'post',
      json: true,
      body: req.body
    },
    (err, response, data) => {
      if (err) {
        return res.json({
          errorCode: 500,
          errorMsg: err
        })
      }
      const signature = sign(new Date().getTime())
      if (data.code === 200) {
        const token = data.data.user_token
        // token 寫入 redis
        redisClient.set(signature, token, err => {
          if(err) {
            console.log('set redis error', err)
          } else {
            console.log('set redis success')
            // 設置過期時間 1小時
            redisClient.expire(signature, 60 * 60)
          }
        })
        // 記錄 cookie 
        res.cookie('nd-token', signature,  { maxAge: 900000 })
      }
      return res.json(data)
    }
  )
})

serverMiddle/index.js  // nuxt.js 提供 serverSide 的中間件入口  在 nuxt.config.js 中配置

const redisClient = require('../redis')
export default function (req, res, next) {
  const sign = req.cookies['nd-token'] || ''
  if (sign) {
    // 獲取當前訪問的 cookie 獲取對應redis 鍵值
    redisClient.get(sign, function (err, hmgeted) {
      if (err) {
        console.log('get redis error ', err)
      } else {
        console.log('get redis success!')
        // 寫入 session層
        req.session.userToken = hmgeted
        // 更新過期時間
        redisClient.expire(sign , 60 * 60)
      }
      next()
    })
    res.cookie('nd-token', sign , { maxAge: 900000 })
  } else {
    next()
  }
}

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import getters from './getters'

Vue.use(Vuex)
const createStore = () => {
  return new Vuex.Store({
    modules: {
      app,
      user
    },
    actions: {
      nuxtServerInit({ commit }, { req, res }) {
        if (req.session.userToken) {
          commit('SET_TOKEN', req.session.userToken)
        } else {
          console.log('nuxtserverinit token no find')
        }
      }
    },
    getters
  })
}

export default createStore

 

  6.2 服務端代碼(nginx負載均衡)

主服務器 A:

vhost/nodeServerA.conf

 server {
     listen 20010; 
     server_name 'nodeServerA.cn';


    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
       add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header Host $http_host;
         proxy_pass http://127.0.0.1:20011; 
    }

}

 server {
     listen 20009; 
     server_name 'nodeServer.cn';
     location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass  http://backend;
    }

}

 

nginx.conf

    upstream backend  {
        server nodeServerA.cn:20010;
        server nodeServerB.cn:20010;
        server nodeServerC.cn:20010;
    }    

服務器B

 server {
     listen 20010; 
     server_name 'nodeServerB.cn';
    
     location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:20011; 
    }
}

服務器C

 server {
     listen 20010; 
     server_name 'nodeServerC.cn';
    
     location / {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:20011; 
    }
}

 

7.說明

  7.1 Nuxt.js 官網 提供的案例是直接 寫入 session 緩存做 store的狀態寫入處理, 這里的問題是 單線程 和 多台服務器均衡負載;

  7.2 我司項目示意圖

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM