/**
* Module dependencies.
*/ var express = require('../..');// ../..是上级目录的上级目录
var hash = require('pbkdf2-password')()
var path = require('path');
var session = require('express-session'); var app = module.exports = express(); // config app.set('view engine', 'ejs');// 定义view引擎,即视图文件后缀名
app.set('views', path.join(__dirname, 'views'));// 定义视图存放的路径,便于node查找 // middleware app.use(express.urlencoded({ extended: false }))
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'shhhh, very secret'
})); /*express-session中间件,session被序列化为json格式,把它当做一个json对象操作就可以了*/ // 保存session消息的中间件 app.use(function(req, res, next){
var err = req.session.error;//取得失败消息
var msg = req.session.success;//取得成功消息
//删除req.session的两个属性
delete req.session.error;
delete req.session.success;
/*res.locals:An object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during that request / response cycle (if any). Otherwise, this property is identical to app.locals. This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on.*/
res.locals.message = '';
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
next();
}); //假装是个数据库,实际只有一个用户名为tj
var users = {
tj: { name: 'tj' }
}; // 创建用户时,需要提供用户名、密码、salt、hash hash({ password: 'foobar' }, function (err, pass, salt, hash) {
if (err) throw err;
// store the salt & hash in the "db"
users.tj.salt = salt;
users.tj.hash = hash;
}); // 认证用户核心代码,需要匹配用户名、hash function authenticate(name, pass, fn) {
if (!module.parent) console.log('authenticating %s:%s', name, pass);//如果没有引用本模块的模块,也就是只运行了这个单文件
var user = users[name];//根据name查询用户
if (!user) return fn(new Error('cannot find user')); hash({ password: pass, salt: user.salt }, function (err, pass, salt, hash) {
if (err) return fn(err);
if (hash === user.hash) return fn(null, user)
fn(new Error('invalid password'));
});
}
/*限制访问中间件,如果浏览器中有session.user,就不限制,否则重定向到login页面*/
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
//默认网站主页重定向到login
app.get('/', function(req, res){
res.redirect('/login');
});
//restricted页面应该是成功登陆后的页面,使用restrict中间件限制访问
app.get('/restricted', restrict, function(req, res){
res.send('Wahoo! restricted area, click to <a href="/logout">logout</a>');
});
/*登出页面,调用session.destory方法(Destroys the session and will unset the req.session property. Once complete, the callback will be invoked.
req.session.destroy(function(err) {
// cannot access session here
}))*/
app.get('/logout', function(req, res){
req.session.destroy(function(){
res.redirect('/');
});
});
//处理用户对login页面的get请求,渲染展示这个页面
app.get('/login', function(req, res){
res.render('login');
});
/*处理用户对login页面的post请求,即通过点击按钮发出而非通过uri的请求*/
app.post('/login', function(req, res){
/*express框架赋予req对象body属性:Contains key-value pairs of data submitted in the request body. By default, it is undefined, and is populated when you use body-parsing middleware such as body-parser and multer.*/
authenticate(req.body.username, req.body.password, function(err, user){
//如果第二个参数不为空,表示登陆成功,设置成功信息
if (user) {
/*Session.regenerate(callback): To regenerate the session simply invoke the method. Once complete, a new SID and Session instance will be initialized at req.session and the callback will be invoked.*/
req.session.regenerate(function(){
req.session.user = user;
req.session.success = 'Authenticated as ' + user.name
+ ' click to <a href="/logout">logout</a>. '
+ ' You may now access <a href="/restricted">/restricted</a>.';
/*A back redirection redirects the request back to the referer, defaulting to / when the referer is missing.
/代表root,back就是返回跳转之前的页面(记录在http请求头里的http referer)
res.redirect('back'); */
res.redirect('back');
});
}
//如果用户不存在,设置失败信息并重定向到登陆页面
else {
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('/login');
}
});
}); /* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

总结一下:这是一个基础的用户认证的express实例。真正显示的页面只有login页面和restricted页面,而在views中只有login模板,restricted页面是写在js中的,而这份代码通过redirect和路由比较简洁地处理了用户可能的各种请求和跳转.

关于登陆成功后有一句res.redirect('back'),一开始我以为没什么用,因为在login.ejs模板中,action是/login,也就是这个页面自己,等于说点了登陆按钮,post请求发给了自己。在我注释掉这一句后,发现结果是下面这样的。至于为什么,还不清楚,可能是因为不能post到本页面。这个程序为了简洁,使用get、post两种方法代表用户两种操作,所以需要这种不正常的操作弥补。

这个程序还有一些漏洞,应该在authenticate函数中添加一段没有错误登陆就删除网站session的语句,否则前一个用户正常登陆,因为还是跳转到了login页面,可以第二次登陆,这时候如果输入错误的用户名和密码,尽管没通过验证,仍然可以通过get方法(uri、跳转)访问到restricted页面,不合逻辑。所以这个跳转到本页面有毒,无论从逻辑上还是程序上都有毒,应该直接重定向到restricted页面,不logout就不让出去。

后来我又想了一下,使用重定向还是不靠谱,用户可以正确登陆后,点击返回,回到登录页再错误登陆。我参考了一下csdn的做法,我正确登陆之后,它新建了一个页面,与原来的页面无关了,但这个时候session中肯定有我的信息了,我在登录页随便点了一个广告,果然是以登陆的状态访问的。但是当我错误登陆之后,再在登陆页点击广告,就变成了以未登陆的状态访问。显然,csdn使用了我上面提到的那种做法,一旦错误登陆,就删除这个页面的登陆状态。

总结一下,解决这种用户状态比较稳妥的做法:

1.另外打开一个页面,不让用户使用返回登录页。

2.其实1不重要,只要用户错误登陆一次,就删除session中保存的信息或者更新session中保存的信息,比如从登陆状态改变为未登录状态,就可以解决这种错误逻辑了。

Express实例代码分析1——简单的用户验证登录文件的更多相关文章

  1. Java不走弯路教程(3.用户验证与文件内容查询)

    3.用户验证与文件内容查询 在上一章中,我们完成了对指定文件内容的输出操作. 我们现在有如下格式的文件product.db id,product_name,product_detail 1,noteb ...

  2. mongodb3.0分片及java代码连接操作测试(开启用户验证)

    最近抽时间搭建了一下mongodb简单的分片,整个过程还算是蛮顺利,只不过在用户验证这一块遇到了一些问题,好在最后终于搞定. 一.服务器搭建过程: 1.安装四个mongodb:一个作为config.一 ...

  3. windows系统,MongoDB开启用户验证登录的正确姿势

    MongoDB默认安装并没有开启用户名密码登录,这样太不安全了,百度出来的开启验证登录的文章,对初次使用MongoDB的小白太不友好了,总结下经验,自己写一份指引. 1,我的安装路径是C:\Progr ...

  4. java web从零单排第二十二期《hibernate》代码分析之查看,删除用户信息

    前两期的内容不知道大家理解的怎么样,我并没有详细的去解释代码的意思,如果你已经自己都钻研明白了,那最好过,但还是一知半解的话,接下来我会仔细分析代码. 1.register.jsp:这部分代码只是简单 ...

  5. 编写代码:ATM的登陆界面(用户验证、主菜单的选择) 查询-- 存款-- 取款-- 退出

    #include <stdio.h>#include <windows.h>int main (void){    int password,one,two,money1=10 ...

  6. Python基础-三次用户验证登录购买商品程序

    需求: 一:三次登录锁定 1.用户信息存放于文件中 2.尝试三次都失败,锁定用户 二.购物车功能要求: 要求用户输入总资产,例如:2000显示商品列表,让用户根据序号选择商品,加入购物车购买,如果商品 ...

  7. Python做域用户验证登录

    安装包 ldap3 代码: from ldap3 import Server, Connection, ALL, NTLM # 连接 server = Server('public.ad.com', ...

  8. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  9. [转载] 常用 Java 静态代码分析工具的分析与比较

    转载自http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代 ...

随机推荐

  1. 搭建vue脚手架

    1.Node.js安装 1.1下载安装 在node.js 官网下载, 根据自己电脑系统安装,一直点下一步即可 1.2测试安装是否成功 Windows+R打开cmd窗口,输入node -v回车出现版本号 ...

  2. win7旗舰版64位GHOST版的,安装telnet客户端时,提示:出现错误。并非所有的功能被成功更改。

    win7旗舰版64位GHOST版的,安装telnet客户端时,提示:出现错误.并非所有的功能被成功更改. 从安装成功的电脑上拷贝ghost版本缺少的文件,然后再安装telnet客户端,我已打包 链接: ...

  3. vue踩坑(二):跨域以及携带cookie

    最近后台需求要在请求的时候传cooki给后台,正常情况下拿到cookie后存在cookie里,同域名下是会自己带到请求头里的,但是因为要在本地调试,那么问题就来了,localhost:8080下面的c ...

  4. Leetcode1000 合并石头的最低成本 区间DP

    有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头. 每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的总数. 找出把所有石头合并成一堆的最低成 ...

  5. Android判断一个点是否在矩形区域内

    个人遇到的问题判断按钮的点击事件还是滑动事件 private boolean button1Down = false; private boolean button2Down = false; pri ...

  6. VLC 用到的那些 YUV 格式

    YUV是视频应用中使用的一类像素格式.YUV实际上是所有“YUV”像素格式共有的颜色空间的名称. 与RGB格式(红 - 绿 - 蓝)相对应,YUV颜色用一个称为Y(相当于灰度)的“亮度”分量和两个“色 ...

  7. Java中关于quartz定时任务时间设置

    每天凌晨2点 0 0 2 * * ?和每天隔一小时 0 * */1 * * ? 例1:每隔5秒执行一次:*/5 * * * * ? 例2:每隔5分执行一次:0 */5 * * * ?在26分.29分. ...

  8. phpstudy 配置端口 和 虚拟域名访问

    打开phpstudy窗口 选择->其他选项菜单->打开配置文件->httpd.conf 添加一个 Listen 8081(端口号) 查看到    Include conf/vhost ...

  9. Linux-Slabinfo

    1.内存管理有两个算法:伙伴算法(buddy system)和slab算法.伙伴算法是以页为单位管理内存,slab算法是以字节为单位管理内存,是内核的小内存管理算法.特点是基于对象进行管理. slab ...

  10. AOP-事物管理

    1. Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务