1. 用户提交包含用户名和密码的表单,发送HTTP请求。
2. 服务器验证用户发来的用户名密码。
3. 如果正确则把当前用户名(通常是用户对象)存储到redis中,并生成它在redis中的ID。 这个ID称为Session ID,通过Session ID可以从Redis中取出对应的用户对象, 敏感数据(比如authed=true)都存储在这个用户对象中。
4. 设置Cookie为sessionId=xxxxxx|checksum并发送HTTP响应, 仍然为每一项Cookie都设置签名。
signedCookie
计算机领域有个名词叫 签名,专业点说,叫 信息摘要算法。
比如,我们现在面临着一个菜鸟开发的网站,他用 cookie 来记录登陆的用户凭证。相应的 cookie 长这样:dotcom_user=alsotang,它说明现在的用户是 alsotang 这个用户。如果我在浏览器中装个插件,把它改成dotcom_user=ricardo,服务器一读取,就会误认为我是 ricardo。然后我就可以进行 ricardo 才能进行的操作了。
之前 web 开发不成熟的时候,用这招甚至可以黑个网站下来,把 cookie 改成 dotcom_user=admin 就行了。
现在是怎么保证不被篡改呢?答案很简单,签个名。
假设我的服务器有个秘密字符串,是 this_is_my_secret_and_fuck_you_all,我为用户 cookie 的 dotcom_user 字段设置了个值 alsotang。cookie 本应是{dotcom_user: 'alsotang'}这样的。
而如果我们签个名,比如把 dotcom_user 的值跟我的 secret_string 做个sha1sha1('this_is_my_secret_and_fuck_you_all' + 'alsotang') === '4850a42e3bc0d39c978770392cbd8dc2923e3d1d'
然后把 cookie 变成这样
{ dotcom_user: 'alsotang', 'dotcom_user.sig': '4850a42e3bc0d39c978770392cbd8dc2923e3d1d', }
这样一来,用户就没法伪造信息了。一旦它更改了 cookie 中的信息,则服务器会发现 hash 校验的不一致。
毕竟他不懂我们的 secret_string 是什么,而暴力破解哈希值的成本太高。
5. 用户收到HTTP响应后,便看不到任何敏感数据了。在此后的请求中发送该Cookie给服务器。
6. 服务器收到此后的HTTP请求后,发现Cookie中有SessionID,进行防篡改验证。
7. 如果通过了验证,根据该ID从Redis中取出对应的用户对象, 查看该对象的状态并继续执行业务逻辑。
下面是写的demo代码,可以跑起来,但是记得安装redis
<!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>
<script src="./lib/jquery-3.3.1.js"></script>
</head>
<body>
<!-- <form> -->
<label for="user">用户名:</label>
<input type="text" id="user" value="">
<label for="pwd">密码:</label>
<input type="password" id="pwd" value="">
<button id="confirm">确定</button>
<!-- </form> -->
<script>
var user = document.getElementById('user')
var pwd = document.getElementById('pwd')
user.addEventListener('change', function (e) {
user.value = e.target.value
})
pwd.addEventListener('change', function (e) {
pwd.value = e.target.value
})
document.getElementById('confirm').addEventListener('click', function (event) {
$.ajax({
type: "POST",
url: "/login",
data: {
name: user.value,
password: pwd.value
},
success: function (data) {
console.log(data)
if (data.ret_code === 0) {
window.location.href = '/home'
}
}
});
})
</script>
</body>
</html>
node index.js
const express = require('express');
const session = require('express-session');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
const cookieParser = require('cookie-parser');
const static = require('express-static')
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
const secret = "wang111";
const sessionName = 'session_id';
var users = require('./user').items;
var findUser = function (name, password) {
return users.find(function (item) {
return item.name === name && item.password === password;
});
};
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/lib', static(__dirname + '/lib'));
// 设置 Cookie
app.use(cookieParser());
// 设置 Session
app.use(session({
secret: secret,
//重新保存:强制会话保存即使是未修改的。默认为true但是得写上
resave: true,
//强制“未初始化”的会话保存到存储
saveUninitialized: false,
name: sessionName,
cookie: { maxAge: 60 * 60 * 24 * 1 },
store: new RedisStore({ // redis-server etc/redis.conf redis-cli
host: '127.0.0.1',
port: '6379',
db: 0,
pass: '',
ttl: 60 * 60 * 24 * 1, //session的有效期为1天(秒)
})
}));
app.get('/', function (req, res) {
if (req.session.login) {
res.redirect('/home');
}
res.sendFile('index.html', {
root: path.join(__dirname, ''),
});
});
app.post("/login", function (req, res) {
var userExist = findUser(req.body.name, req.body.password);
if (userExist) {
req.session.user = req.body.name;
req.session.login = true;
res.json({ ret_code: 0, ret_msg: '登录成功' });
} else {
res.json({ ret_code: 1, ret_msg: '账号或密码错误' });
}
});
app.get('/home', function (req, res) {
res.sendFile('home.html', {
root: path.join(__dirname, ''),
});
});
app.get('/logout', function(req, res){
req.session.destroy();
res.redirect('/');
})
app.listen(9080);
node user.js
module.exports = {
items: [
{name: 'wang', password: '123'}
]
};