这次来做一个站点登录的小样例,后面会用到。

这个演示样例会用到Cookie、HTML表单、POST数据体(body)解析。

第一个版本号,我们的用户数据就写死在js文件中。

第二个版本号会引入MongoDB来保存用户数据。

演示样例准备

1. 使用express创建应用

就以下的命令序列:

express LoginDemo
cd LoginDemo
npm install

2. 登录页面

登录页面的jade模板为login.jade,内容例如以下:

doctype html
html
head
meta(charset='UTF-8')
title 登录
link(rel='stylesheet', href='/stylesheets/login.css')
body
.form-container
p.form-header 登录
form(action='login', method='POST', align='center')
table
tr
td
label(for='user') 账号:
td
input#user(type='text', name='login_username')
tr
td
label(for='pwd') 密码:
td
input#pwd(type='password', name='login_password')
tr
td(colspan='2', align='right')
input(type='submit', value='登录')
p #{msg}

login.jade放在views文件夹下。我在login.jade里硬编码了汉字,注意文件用UTF-8编码。

这个模板的最后是一条动态消息,用于显示登录错误信息,msg变量由应用程序传入。

我给login页面写了个简单的CSS。login.css文件,内容例如以下:

form {
margin: 12px;
}
a {
color: #00B7FF;
} div.form-container {
display: inline-block;
border: 6px solid steelblue;
width: 280px;
border-radius: 10px;
margin: 12px;
} p.form-header {
margin: 0px;
font: 24px bold;
color: white;
background: steelblue;
text-align: center;
} input[type=submit]{
font: 18px bold;
width: 120px;
margin-left: 12px;
}

请把login.css放在public/stylesheets文件夹下。

3. profile页面

登录成功后会显示配置页面。profile.jade页面内容:

doctype html
html
head
meta(charset='UTF-8')
title= title
body
p #{msg}
p #{lastTime}
p
a(href='/logout') 退出

profile.jade放在views文件夹下。profile页面显示一条登录成功的消息。还显示上次登录时间,最后提供了一个退出链接。

4. app.js修改

我修改了app.js。以便用户在没有登录时訪问站点自己主动跳转到login页面。

新的app.js内容例如以下:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser'); var users = require('./routes/users'); var app = express(); // view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); // uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.all('*', users.requireAuthentication);
app.use('/', users); // catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
}); // error handlers // development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
} // production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
}); module.exports = app;

5. users.js

我修改了users.js,把认证、登录、登出等逻辑放在里面,首先要把users.js转为UTF-8编码(sorry。硬编码了汉字哈)。内容:

var express = require('express');
var router = express.Router();
var crypto = require('crypto'); function hashPW(userName, pwd){
var hash = crypto.createHash('md5');
hash.update(userName + pwd);
return hash.digest('hex');
} // just for tutorial, it's bad really
var userdb = [
{
userName: "admin",
hash: hashPW("admin", "123456"),
last: ""
},
{
userName: "foruok",
hash: hashPW("foruok", "888888"),
last: ""
}
]; function getLastLoginTime(userName){
for(var i = 0; i < userdb.length; ++i){
var user = userdb[i];
if(userName === user.userName){
return user.last;
}
}
return "";
} function updateLastLoginTime(userName){
for(var i = 0; i < userdb.length; ++i){
var user = userdb[i];
if(userName === user.userName){
user.last = Date().toString();
return;
}
}
} function authenticate(userName, hash){ for(var i = 0; i < userdb.length; ++i){
var user = userdb[i];
if(userName === user.userName){
if(hash === user.hash){
return 0;
}else{
return 1;
}
}
} return 2;
} function isLogined(req){
if(req.cookies["account"] != null){
var account = req.cookies["account"];
var user = account.account;
var hash = account.hash;
if(authenticate(user, hash)==0){
console.log(req.cookies.account.account + " had logined.");
return true;
}
}
return false;
}; router.requireAuthentication = function(req, res, next){
if(req.path == "/login"){
next();
return;
} if(req.cookies["account"] != null){
var account = req.cookies["account"];
var user = account.account;
var hash = account.hash;
if(authenticate(user, hash)==0){
console.log(req.cookies.account.account + " had logined.");
next();
return;
}
}
console.log("not login, redirect to /login");
res.redirect('/login? '+Date.now());
}; router.post('/login', function(req, res, next){
var userName = req.body.login_username;
var hash = hashPW(userName, req.body.login_password);
console.log("login_username - " + userName + " password - " + req.body.login_password + " hash - " + hash);
switch(authenticate(userName, hash)){
case 0: //success
var lastTime = getLastLoginTime(userName);
updateLastLoginTime(userName);
console.log("login ok, last - " + lastTime);
res.cookie("account", {account: userName, hash: hash, last: lastTime}, {maxAge: 60000});
res.redirect('/profile?'+Date.now());
console.log("after redirect");
break;
case 1: //password error
console.log("password error");
res.render('login', {msg:"密码错误"});
break;
case 2: //user not found
console.log("user not found");
res.render('login', {msg:"用户名不存在"});
break;
}
}); router.get('/login', function(req, res, next){
console.log("cookies:");
console.log(req.cookies);
if(isLogined(req)){
res.redirect('/profile?'+Date.now());
}else{
res.render('login');
}
}); router.get('/logout', function(req, res, next){
res.clearCookie("account");
res.redirect('/login?'+Date.now());
}); router.get('/profile', function(req, res, next){
res.render('profile',{
msg:"您登录为:"+req.cookies["account"].account,
title:"登录成功",
lastTime:"上次登录:"+req.cookies["account"].last
});
}); module.exports = router;

如你所见,我内置了两个账号。admin和foruok,登录时就验证这两个账号,不正确就报错。

好了,运行“npm start”,然后在浏览器里打开“http://localhost:3000”,能够看到以下的效果:

折腾几次,登录,退出。再次登录,效果例如以下:

好啦,这就是这个演示样例的效果。

接下来我们来解释一下用到概念和部分代码。

处理POST正文数据

我们在演示样例中使用了HTML表单来接收username与password。当input元素的类型为submit时。点击它,浏览器会把表单内的数据按一定的格式组织之后编码进body,POST到指定的server地址。

username与password,在server端,能够通过HTML元素的名字属性的值找出来。

server解析表单数据这一过程,我们不用操心,用了express的body-parser中间件。它会帮我们做这件事。仅仅要做简单的配置就可以。

并且这些配置代码,express generator都帮我们完毕了,例如以下:

//载入body-parser模块
var bodyParser = require('body-parser');
...
//应用中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

我们处理/login路径上的POST请求的代码在users.js里,从“router.post(‘/login’…”開始(94行,要是markdown能自己主动给代码插入行号就好了)。

引用登录表单内的用户名的代码例如以下:

var userName = req.body.login_username;

注意到了吧,express.Request对象req内有解析好的body,我们使用login_username来訪问用户名。而login_username就是我们在HTML里的input元素的name属性的值。就这么关联的。password也相似。

cookie

cookie,按我的理解。就是server发给浏览器的一张门票。要訪问server内容,能够凭票入场,享受某种服务。server能够在门票上记录一些信息。从技术角度讲,想记啥记啥。当浏览器訪问server时,HTTP头部把cookie信息带到server。server解析出来。校验当时记录在cookie里的信息。

HTTP协议本身是无状态的,而应用server往往想保存一些状态,cookie应运而生。由server颁发。通过HTTP头部传给浏览器。浏览器保存到本地。兴许訪问server时再通过HTTP头部传递给server。这样的交互,server就能够在cookie里记录一些用户相关的信息。比方是否登录了。账号了等等,然后就能够依据这些信息做一些动作。比方我们演示样例中的持久登录的实现,就利用了cookie。另一些电子商务站点,实现购物车时也可能用到cookie。

cookie存储的是一些key-value对。

在express里,Request和Response都有cookie相关的方法。Request实例req的cookies属性。保存了解析出的cookie,假设浏览器没发送cookie,那这个cookies对象就是一个空对象。

express有个插件,cookie-parser,能够帮助我们解析cookie。

express生成的app.js已经自己主动为我们配置好了。相关代码:

var cookieParser = require('cookie-parser');
...
app.use(cookieParser());

express的Response对象有一个cookie方法,能够回写给浏览器一个cookie。

以下的代码发送了一个名字叫做“account”的cookie,这个cookie的值是一个对象,对象内有三个属性。

res.cookie("account", {account: userName, hash: hash, last: lastTime}, {maxAge: 60000});

res.cookie()方法原型例如以下:

res.cookie(name, value [, options])

文档在这里:http://expressjs.com/4x/api.html#res.cookie

浏览器会解析HTTP头部里的cookie,依据过期时间决定保存策略。当再次訪问server时,浏览器会把cookie带给server。

server使用cookieParser解析后保存在Request对象的cookies属性里。req.cookies本身是一个对象。解析出来的cookie,会被关联到req.cookies的以cookie名字命名的属性上。比方演示样例给cookie起的名字叫account,服务端解析出的cookie,就能够通过req.cookies.account来訪问。注意req.cookies.account本身既可能是简单的值也可能是一个对象。在演示样例中通过res.cookie()发送的名为account的cookie,它的值是一个对象,在这样的情况下,server这边从HTTP请求中解析出的cookie也会被组装成一个对象。所以我们通过req.cookies.account.account就能够拿到浏览器通过cookie发过来的用户名。但假设浏览器没有发送名为“account”的cookie,那req.cookies.account.hash这样的訪问就会抛异常。所以我在代码里使用req.cookies[“account”]这样的方式来检測是否有account这个cookie。

持久登录

假设用户每次訪问一个须要鉴权的页面都要输入username与password来登录。那就太麻烦了。所以,非常多现代的站点都实现了持久登录。我的演示样例使用cookie简单实现了持久登录。

在处理/login路径上的POST请求时。假设登录成功。就把用户名、一个hash值、还有上次登录时间保存在cookie里,并且设置cookie的有效期为60秒。这样在60秒有效期内。浏览器兴许的訪问就会带cookie,服务端代码从cookie里验证用户名和hash值,让用户保持登录状态。当过了60秒。浏览器就不再发送cookie,服务端就觉得须要又一次登录。将用户重定向到login页面。

如今服务端的用户信息就简单的放在js代码里了。非常丑陋,下次我们引入MongoDB,把用户信息放在数据库里。


其他文章:

Node.js开发入门—使用cookie保持登录的更多相关文章

  1. Node.js开发入门—使用AngularJS

    做一个Web应用,一般都有前台和后台,Node.js能够实现后台.利用jade模板引擎也能够生成一些简单的前台页面,但要想开发出具有实际意义的现代Web应用.还得搭配一个Web前端框架. Angula ...

  2. Node.js开发入门—HelloWorld再分析

    在Node.js开发入门(1)我们用http模块实现了一个简单的HelloWorld站点,这次我们再来细致分析下代码.了解很多其它的细节. 先看看http版本号的HelloWorld代码: 代码就是这 ...

  3. Node.js开发入门—套接字(socket)编程

    Node.js的net模块提供了socket编程接口,方便我们利用较为底层的套接字接口来实现应用协议.这次我们看一个简单的回显服务器示例,包括服务端和客户端的代码. 代码 分服务器和客户端两部分来说吧 ...

  4. Node.js快速入门

    Node.js是什么? Node.js是建立在谷歌Chrome的JavaScript引擎(V8引擎)的Web应用程序框架. 它的最新版本是:v0.12.7(在编写本教程时的版本).Node.js在官方 ...

  5. iKcamp新书上市《Koa与Node.js开发实战》

    内容摘要 Node.js 10已经进入LTS时代!其应用场景已经从脚手架.辅助前端开发(如SSR.PWA等)扩展到API中间层.代理层及专业的后端开发.Node.js在企业Web开发领域也日渐成熟,无 ...

  6. Node.js学习笔记——Node.js开发Web后台服务

    一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...

  7. Linux虚拟机中 Node.js 开发环境搭建

    Node.js 开发环境搭建: 1.下载CentOS镜像文件和VMWare虚拟机程序; 2.安装VMWare——>添加虚拟机——>选择CentOS镜像文件即可默认安装带有桌面的Linux虚 ...

  8. heX——基于 HTML5 和 Node.JS 开发桌面应用

    heX 是网易有道团队的一个开源项目,允许你采用前端技术(HTML,CSS,JavaScript)开发桌面应用软件的跨平台解决方案.heX 是你开发桌面应用的一种新的选择,意在解决传统桌面应用开发中繁 ...

  9. 推荐近期15个 Node.js 开发工具

    近来Node.js 越来月流行了,这个基于Google V8 引擎建立的平台, 用于方便地搭建响应速度快.易于扩展的网络应用.在本文中,我们列出了2015年最佳的15个 Node.js 开发工具.这些 ...

随机推荐

  1. SpringBoot添加支持CORS跨域访问

    原文:https://www.jianshu.com/p/c6ea21b64f6e CORS(Cross-Origin Resource Sharing)"跨域资源共享",是一个W ...

  2. Tor Browser(洋葱浏览器)——一款使你匿名上网的浏览器

    第一次知道Tor是在有关斯诺登的新闻报道中. 简单说Tor是一款是你匿名访问网络的的软件.用户通过Tor可以在因特网上进行匿名交流. 为了实现匿名目的,Tor把分散在全球的计算机集合起来形成一个加密回 ...

  3. Jacob调用COM组件总结,实例

    转自:http://blog.csdn.net/whw6_faye/article/details/5418506 最近做了一个Java Jacob调用COM组件的东西,其中遇到了不少问题,现在把经验 ...

  4. Jacob的使用出错总结

    转自:http://blog.163.com/wm_at163/blog/static/13217349020114166447941/ Jacob的使用方法: 1.在工程中导入 jacob.jar ...

  5. Computer Vision Tutorials from Conferences (1) -- ICCV

    ICCV 2013 (http://www.iccv2013.org/tutorials.php) Don't Relax: Why Non-Convex Algorithms are Often N ...

  6. myeclipse使用maven教程

    本教程包括 1.使用myeclipse构建maven下载jar包 2.使用myeclipse运行maven命令 3.使用myeclipse管理maven项目 搭建maven教程以后有时间了贴进来. 1 ...

  7. MongoDB 数据迁移 备份 导入(自用)

    MongoDB bin文件夹下 备份:mongodump -h IP:PORT -d 库名 -c 集合名 -o 存储路径 恢复:mongorestore -h IP:PORT -d 库名 -c 集合名 ...

  8. Sqlite数据库字符串处理函数replace

    Sqlite 字符串处理函数replace官方说明: replace(X,Y,Z) The replace(X,Y,Z) function returns a string formed by sub ...

  9. [AngularJS] Angular 1.3 ng-model-options - getterSetter

    getterSetter:  boolean value which determines whether or not to treat functions bound to ngModel as ...

  10. [AngularJS] Angular1.3 ngAria - 1

    Accessibility is an often overlooked essential feature of a web application. a11y Is a critical comp ...