單點登錄的實現方式有很多種,在這裏博主就先使用了簡單的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);