原创转载请注明出处:http://agilestyle.iteye.com/blog/2355955
cookie 和 session
HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据,如何能把一个用户的状态数据关联起来呢?
比如在天猫的某个页面中,你进行了登陆操作。当你跳转到商品页时,服务端如何知道你是已经登陆的状态?
cookie

Express Cookie
https://www.npmjs.com/package/cookie-parser
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.listen(3000);
app.use(cookieParser());
app.get('/', function(req, res) {
if (req.cookies.isVisit) {
console.log(req.cookies);
res.send("Welcome Again");
} else {
res.cookie('isVisit', 1, {
maxAge: 60 * 1000
});
res.send("Welcome First");
}
});
Session

Express Session
https://www.npmjs.com/package/express-session
var express = require('express');
var session = require('express-session');
var app = express();
app.listen(3000);
app.use(session({
secret: 'recommend to use 128 bytes random string',
cookie: {
maxAge: 60 * 1000
}
}));
app.get('/', function(req, res) {
if (req.session.isVisit) {
req.session.isVisit++;
res.send('you viewed this page ' + req.session.isVisit + ' times');
} else {
req.session.isVisit = 1;
res.send("welcome first");
console.log(req.session);
}
});
Demo
使用cookie-parser和express-session实现一个用户持久登录的Demo
Project Directory

src
package.json
{
"name": "node-cookie-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.16.0",
"cookie-parser": "^1.4.3",
"crypto-js": "^3.1.9-1",
"ejs": "^2.5.5",
"express": "^4.14.1",
"express-session": "^1.15.0",
"install": "^0.8.7",
"npm": "^4.1.2",
"parseurl": "^1.3.1",
"path": "^0.12.7"
}
}
index.js
var express = require('express');
var ejs = require('ejs');
var path = require('path');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var routes = require('./routes/routes');
var app = express();
app.engine('.html', ejs.__express);
app.set("port", process.env.PORT || 3000);
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "html");
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 60 * 1000
}
}));
app.use(routes);
app.listen(app.get('port'), function() {
console.log('Server started on port ' + app.get('port'));
});
Note:
将ejs模板后缀.ejs改为.html

使用body-parser、cookie-parser和express-session

routes.js
var express = require('express');
var CryptoJS = require("crypto-js");
var parseurl = require('parseurl');
var router = express.Router();
// mock-up user info start
var userDB = [{
username: 'admin',
password: md5('123456'),
lastLogin: ''
}, {
username: 'node',
password: md5('123456'),
lastLogin: ''
}, {
username: 'express',
password: md5('123456'),
lastLogin: ''
}];
// mock-up user info end
function md5(password) {
return CryptoJS.MD5(password).toString();
}
function getLastLoginTime(username) {
for (var i in userDB) {
if (username === userDB[i].username) {
return userDB[i].lastLogin;
}
}
return "";
}
function updateLastLoginTime(username) {
for (var i in userDB) {
if (username === userDB[i].username) {
userDB[i].lastLogin = Date().toString();
}
}
}
function authenticateUserInfoFromDB(username, password) {
for (var i in userDB) {
if (username === userDB[i].username) {
if (password === userDB[i].password) {
return 1;
} else {
return 0;
}
}
}
return -1;
}
function isAuthenticated(req, res) {
if (typeof req.cookies.account === 'undefined' || req.cookies.account === null) {
return false;
}
if (req.cookies.account !== null && req.cookies.account !== '') {
var account = req.cookies.account;
if (authenticateUserInfoFromDB(account.username, account.password) === 1) {
console.log(req.cookies.account.username + " logged in.");
return true;
}
}
return false;
}
function ensureAuthenticated(req, res, next) {
req.session.url = parseurl(req).pathname;
console.log(req.session.url);
if(isAuthenticated(req, res)) {
next();
} else {
res.redirect('/login?' + Date.now());
}
}
// router.use(function (req, res, next) {
// res.locals.msg = req.msg;
// next();
// });
router.get('/', function(req, res) {
res.render('login');
});
router.get('/login', function(req, res) {
if (isAuthenticated(req, res)) {
res.redirect('/welcome?' + Date.now());
} else {
res.render('login');
}
});
router.post('/login', function(req, res, next) {
var username = req.body.username;
var password = md5(req.body.password);
var url = req.session.url;
console.log('in login url ' + url);
var result = authenticateUserInfoFromDB(username, password);
if (result === 1) {
var lastLogin = getLastLoginTime(username);
updateLastLoginTime(username);
res.cookie('account', {
username: username,
password: password,
lastLogin: lastLogin
}, {
maxAge: 60 * 1000
});
console.log('in login ' + req.session.url);
if (typeof url !== 'undefined' && url !== null && url !== '') {
res.redirect(url + '?' + Date.now());
} else {
res.redirect('/welcome?' + Date.now());
}
} else if (result === 0) {
res.render('login', {
msg: 'password incorrect'
});
} else if (result === -1) {
res.render('login', {
msg: 'username not exist'
});
}
});
router.get('/logout', function(req, res, next) {
res.clearCookie('account');
req.session.url = '';
res.redirect('/login?' + Date.now());
});
router.get('/welcome', ensureAuthenticated, function(req, res, next) {
res.render('welcome', {
msg: 'username: ' + req.cookies.account.username,
title: 'Login Success',
lastLogin: 'lastLogin: ' + (req.cookies.account.lastLogin === '' ? 'N/A' : req.cookies.account.lastLogin)
});
});
router.get('/profile', ensureAuthenticated, function(req, res, next) {
res.render('profile');
});
router.get('/messages', ensureAuthenticated, function(req, res, next) {
res.render('messages');
});
module.exports = router;
Note:
这里仅仅是为了Demo,所以不操作数据库,仅仅mockup一下

使用md5加密password、获取最后登录时间、更新最后登录时间

验证输入的用户名和密码是否有效,1:有效 0:密码错 -1:用户不存在

这里采用将username和password存如一个名为account的cookie,将未登录前的输入的url存入session用于页面跳转

login的get和post路由表,其中post请求中将username和password存入名为account的cookie,时间为1分钟

welcome、profile、messages的get路由表,其中ensureAuthenticated起到了一个拦截器的作用

_footer.html
</body>
</html>
_header.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
<script type="text/javascript" src="//code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
login.html
<% include _header.html %>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div class="page-header">
<div class="alert alert-info" role="alert">
<h4>This demo shows how to use cookie-parser and express-session</h4>
<ul>
<li>
<a href="https://www.npmjs.com/package/cookie-parser" class="alert-link">cookie-parser</a>
</li>
<li>
<a href="https://www.npmjs.com/package/express-session" class="alert-link">express-session</a>
</li>
</ul>
</div>
<% if(locals.msg) {%>
<div class="alert alert-warning" role="alert">
<p><%= locals.msg %></p>
</div>
<% } %>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Log in</h3>
</div>
<div class="panel-body">
<form action="/login" method="post" class="form-horizontal">
<div class="form-group">
<label class="col-sm-4 control-label" for="username">Username</label>
<div class="col-sm-5">
<input type="text" class="form-control" id="username" name="username"
placeholder="Username" required="required" autofocus="autofocus">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label" for="password">Password</label>
<div class="col-sm-5">
<input type="password" class="form-control" id="password" name="password"
placeholder="Password" required="required">
</div>
</div>
<div class="form-group">
<div class="col-sm-5 col-sm-offset-4">
<input type="submit" value="Log in" class="btn btn-primary">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<% include _footer.html %>
Note:
如果不想在html页面中使用locals.msg

可以在routes.js中定义如下方法,之后在页面上可以直接使用 msg 即可取到值,但是这仅限于login.html页面,此msg是req.locals.msg, 与余下几个页面的 msg 无关
router.use(function (req, res, next) {
res.locals.msg = req.msg;
next();
});
messages.html
<% include _header.html %>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div class="page-header">
<div class="alert" role="alert">
<ul class="nav nav-pills">
<li role="presentation" class="active"><a href="/welcome">Welcome</a></li>
<li role="presentation"><a href="/profile">Profile</a></li>
<li role="presentation"><a href="/messages">Messages</a></li>
<li role="presentation"><a href="/logout">Logout</a></li>
</ul>
</div>
<div class="alert alert-success" role="alert">
<h2>In Messages Page</h2>
</div>
</div>
</div>
</div>
</div>
<% include _footer.html %>
profile.html
<% include _header.html %>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div class="page-header">
<div class="alert" role="alert">
<ul class="nav nav-pills">
<li role="presentation" class="active"><a href="/welcome">Welcome</a></li>
<li role="presentation"><a href="/profile">Profile</a></li>
<li role="presentation"><a href="/messages">Messages</a></li>
<li role="presentation"><a href="/logout">Logout</a></li>
</ul>
</div>
<div class="alert alert-success" role="alert">
<h2>In Profle Page</h2>
</div>
</div>
</div>
</div>
</div>
<% include _footer.html %>
welcome.html
<% include _header.html %>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div class="page-header">
<div class="alert" role="alert">
<ul class="nav nav-pills">
<li role="presentation" class="active"><a href="/welcome">Welcome</a></li>
<li role="presentation"><a href="/profile">Profile</a></li>
<li role="presentation"><a href="/messages">Messages</a></li>
<li role="presentation"><a href="/logout">Logout</a></li>
</ul>
</div>
<div class="alert alert-success" role="alert">
<h2><%= title %></h2>
<h4><%= msg %></h4>
<h4><%= lastLogin %></h4>
</div>
</div>
</div>
</div>
</div>
<% include _footer.html %>
Test
http://localhost:3000/

password incorrect

login success

Note:
默认登录成功话会直接跳转welcome页面

查看welcome的cookie信息

等1分钟后,在点击Profile Tab,会提示重新登录,重新输入用户名和密码之后,会直接跳转到Profle页面

Message页面

Reference
http://expressjs.com/en/4x/api.html
https://www.npmjs.com/package/cookie-parser
https://www.npmjs.com/package/express-session
https://github.com/alsotang/node-lessons/tree/master/lesson16
本文介绍了一个使用Node.js实现的用户持久登录示例,通过cookie-parser和express-session中间件完成登录状态的维护。
857

被折叠的 条评论
为什么被折叠?



