Express Cookie & Session

原创转载请注明出处: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

扫描二维码关注公众号,回复: 519236 查看本文章
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

猜你喜欢

转载自agilestyle.iteye.com/blog/2355955