使用redis+cookie做單點登錄(node+express)

單點登錄的實現方式有很多種,在這裏博主就先使用了簡單的cookie+redis做了一個單點登錄
注:沒有絕對的安全,所以我們加強驗證,增加攻擊者攻破的難度

我的思路是這樣的
1:首先用戶輸入賬號密碼登錄後在數據庫進行對比
2:賬號密碼錯誤重新登錄,賬號密碼正確則進行下一步
3:cookie在同一個瀏覽器中是共享的,在用戶登錄成功之後,我們使用加密算法進行加密混淆形成一個token存入cookoie中,鍵爲自定義標識字段(比如userSSH),值爲token(不要在cookie中存儲重要信息,容易被破解,所以在這裏我們存入token,這個token就是驗證用戶信息的媒介
4:cookie已經形成了,這時候用到了redis,就好比一個驗證用戶中心數據庫一樣,我們將形成的cookie的值,就是這個token存入redis中,鍵爲token,值爲用戶的信息,並設置過期銷燬的時間
5:在這裏我們已經形成了cookie和redis用戶信息,用戶使用其他域名訪問的時候,首先驗證是否有userSSH這個cookie,如果沒有,則引導至登錄界面
6:如果有這個cookie,則把這個cookie的值token取出,用這個憑證去和redis中的信息對比,憑證生效,用戶可以正常操作,並刷新redis中該數據的過期時間,憑證無效,則引導至登錄界面
7:至此,redis+cookie實現的單點登錄算是完成了,但在實際應用中一定遠遠比這個複雜,尤其是安全考慮方面

前端代碼:簡單的form表單

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div style="border:3px solid green;width: 500px;height: 500px;">
        登錄界面
        <form action="http://localhost:3000/userLogin" method="POST">
            用戶名:<input type="username" value="" name="username">
            密碼:<input type="password" value="" name="password">
            <button type="submit">提交</button>
        </form>
    </div>
</body>

</html>

服務端工程目錄結構:
在這裏插入圖片描述
3000端口:

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const mongoIndex = require('../mongodb-config/index');
const token = require('../token-config/userToken');
const typeData = require('../baseData/typeData');
const session = require('express-session');
const cookie = require('cookie-parser');
const redis = require('../redis-config/redis-config')
app.use(cookie());
//session/cookie中間件
app.use(session({
  secret: 'zzw',//對session id相關的cookie進行簽名
  resave: false,
  saveUninitialized: false,//是否保存未初始化的的會話
  rolling: true,
  cookie: {
    maxAge: 1000 * 60 * 3//設置session存活時間,單位毫秒
  }
}));

/**
 * 
 */
/* GET home page. */
app.get('/index', async (req, res, next) => {
  console.log(req.cookies);
  if (await typeData.typeData(req.session.userSSH)) {
    console.log('用戶的session存在,可以訪問');
    res.redirect('/enter');
  } else if (await typeData.typeData(req.cookies.userSSH)) {
    console.log('用戶的cookie存在,需要驗證');
    res.redirect('/enterToken');
  } else {
    console.log('用戶的cookie不存在,需要登錄');
    res.redirect('../login.html');
  }
});
app.post('/userLogin', async (req, res, next) => {
  const user = await mongoIndex.mongoUser.find(
    {
      username: req.body.username,
      password: req.body.password
    },
    '_id username password',
  ).catch((err) => {
    if (err) {
      res.send('error');
    }
  });

  if (await typeData.typeData(user)) {//判斷是否有效
    let userResult = {
      username: user[0].username,
      password: user[0].password
    };
    res.cookie('userSSH', token);//將用戶信息存在cookie中(鍵爲標識字段,值爲token)
    await mongoIndex.mongoUser.updateOne(
      {
        _id: mongoose.Types.ObjectId(user[0]._id)
      },
      {
        $set:
        {
          user_ssh: token
        }
      }
    );
    redis.set(token, JSON.stringify(userResult));//將用戶的賬號密碼轉換存入redis(鍵爲token,值爲用戶信息)
    redis.PEXPIRE(token, 1000 * 60 * 3);//設置過期時間並刪除
    redis.get(token, (err, data) => {
      console.log(JSON.parse(data));
    });
    //存入redis數據庫中用於數據共享
    res.redirect('/enter');
  } else {
    res.redirect('../login.html');
  }
});
//cookie認證中心
app.get('/enterToken', async (req, res, next) => {  
  redis.get(req.cookies.userSSH, async (err, data) => {
    if (await typeData.typeData(data)) {//如果該cookie的憑證有效
      req.session.userSSH = req.cookies.userSSH;//則重置sesssion
      redis.PEXPIRE(req.session.userSSH, 1000 * 60 * 3);//重新設置過期時間
      res.redirect('/enter');
    } else {
      console.log('憑證無效,session和redis已到期,需要登錄');
      res.redirect('../login.html');//如果該cookie憑證無效,則引導用戶登錄
    }
  });
});
app.get('/enter', (req, res, next) => {
  res.send('用戶的cookie認證成功,已生成session,可以正常訪問');
});
module.exports = app;

redis配置:

const redis = require('redis');
const redisServer = redis.createClient('6379', '127.0.0.1');

redisServer.on('connect', () => {
    console.log('redis連接成功',);
});
redisServer.on('error', () => {
    console.log('redis連接異常');
});
module.exports = redisServer;

mongodb配置:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/user_test', { useNewUrlParser: true, poolSize: 5 });

//判斷連接是否生效
const conn = mongoose.connection;

conn.on("open", () => {
    console.log('mongodb連接成功');
})
conn.on("error", () => {
    console.log('mongodb連接失敗');
});

mongoose骨架配置:

const mongoose = require('mongoose');

//需要生成可以操作表數據的對象和Schema
const user = mongoose.Schema({
    username: { type: String },
    password: { type: String },
    user_ssh: { type: String }
}, { collection: "user", versionKey: false, strict: true });

module.exports = mongoose.model('user', user);



token配置:

const crypto = require('crypto');

/**
 * 生成令牌和token
 * @return {string} return 返回值
 */
function getToken(){
    let buf = crypto.randomBytes(12);
    let token = buf.toString('hex');
    return token;
}
module.exports = getToken();

基礎驗證配置:

class typeData {

async typeData(data) {
    if (data == '' ||
        data == null ||
        data == false ||
        data == undefined ||
        data == [] ||
        data == {}) {
        return false;
    } else {
        return true;
    }
}

};
module.exports = new typeData();

app.js的配置(增加了mongodb和redis的初始化)

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
const mongo = require('./mongodb-config/mongoConfig');
const redis = require('./redis-config/redis-config')
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

另一個測試藉口:3100

const express = require('express');
//const session = require('express-session');
const cookie = require('cookie-parser');
const app = express();
const redis = require('redis');
const redisServer = redis.createClient('6379', '127.0.0.1');
const typeData = require('./baseData/typeData');
redisServer.on('connect', () => {
  console.log('redis連接成功');
});
redisServer.on('error', () => {
  console.log('redis連接異常');
});
app.use(cookie());
//session/cookie中間件
/*app.use(session({
  secret: 'zzw',//對session id相關的cookie進行簽名
  resave: false,
  saveUninitialized: false,//是否保存未初始化的的會話
  rolling: true,
  cookie: {
    maxAge: 1000 * 60 * 3//設置session存活時間,單位毫秒
  }
}));*/

//先判斷cookie是否存在
app.get('/index', async (req, res, next) => {
  if (await typeData.typeData(req.cookies.userSSH)) {
    res.redirect('/cookieSuccess?cookieData=' + req.cookies.userSSH);
  } else {
    res.redirect('/cookieErr?cookieData=' + req.cookies.userSSH);
  }
});
app.get('/cookieErr', (req, res, next) => {
  res.send('用戶的cookie標識不存在!!');
  //用戶的cookie標識不存在證明用戶沒有登錄,此時可以引導至登錄界面
});
//在用這個憑證和redis中的憑證進行對比
app.get('/cookieSuccess', (req, res, next) => {
  redisServer.get(req.query.cookieData, (err, data) => {
    console.log('data  = ', data);
    res.send({ data: data, token: req.query.cookieData });
  });
})
app.listen(3100);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章