单点登录的实现方式有很多种,在这里博主就先使用了简单的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);