文档章节

无密码身份验证:安全、简单且部署快速

OneAPM蓝海讯通
 OneAPM蓝海讯通
发布于 2016/04/07 17:24
字数 2835
阅读 43
收藏 3

Passwordless authentication: Secure, simple, and fast to deploy

【编者按】本文作者为 Florian HeinemannRobert Nyman。Florian 来自 MIT 系统设计与管理学院,专注于复杂的社交技术系统。此前曾在企业软件领域的多家初创公司工作,之后加入 Airbus,担任知识与创新管理经理一职。Robert 是 Mozilla Hacks 技术传道师及编辑。曾就 HTML5,JavaScript 以及 Open Web 发表过多次谈话与博文。Robert 坚定地看好 HTML5 与 Open Web,自1995年就开始在 Front End 开发部门研究 Web 技术。

本文系 OneAPM 工程师编译呈现,以下为正文。

Passwordless(无密码)是用于 Node.js 程序的一种身份验证中间件,能提高用户安全水平,同时具备部署简单、快速的特点。

过去几个月,对热衷于 Web 安全与保密性的人来说,着实激动人心:出现了许多了不起的文章讨论,还有许多事件,都在提高人们的安全意识。

然而,大多数网站仍在使用最早期的 web 身份验证方式:用户名与密码。

尽管用户名密码这种身份验证方式的确占据了一席之地,但如果以为这是所有项目的终极选择,我们便应该更加谨慎了。我们知道,大多数人在访问网站时都使用同一套密码。对于那些缺少安全专家支持的 web 项目,如果用户在该网站的密码遭到泄露,那就可能伤及他的 Amazon 账户,我们真的要让用户承担这种风险么?此外,这种经典的身份验证机制至少存在两种攻击角度:登录页与密码找回页。而且,后者的实现往往在匆忙中进行,因而风险更高。

最近,我们看到了许多不错的点子。笔者尤其对一个直观而且低技术含量的解决方法感兴趣:一次性密码。这种方法部署快速,攻击面小,而且不需要 QR codes 或 JavaScript。无论何时,用户想要登录或使之前的会话失效,都可以通过电子邮件或短信息收到一个短时间有效的一次性链接与令牌(token)。如果你想试一试,可以下载passwordless.net中的演示代码。

不幸的是,由于技术栈的差别,基本上不存在现成的解决方案。因此,Passwordless 针对 Node.js 做出了一些改动。

从 Node.js 与 Express 入手

Passwordless 入门非常简单。两个小时以内,你就能学会部署全面且安全的身份验证解决方案:

$ npm install passwordless --save

获取基本的框架。你还要安装某个现成的存储接口,比如 MongoStore,用于安全地存储令牌。

$ npm install passwordless-mongostore --save

在传送令牌给用户时,电子邮件通常是最好的选择(不过,短消息也可以)。你可以任意选择邮件框架,比如:

$ npm install emailjs --save

基本设置

首先,请求上文用到的所有模块,将它们放在用于初始化 Express 的同一个文件中:

var passwordless = require('passwordless');
var MongoStore = require('passwordless-mongostore');
var email   = require("emailjs");

如果你选用 emailjs 进行令牌传递,此时应该与邮箱账户进行连接(比如:Gmail 账户):

var smtpServer  = email.server.connect({
   user:    yourEmail,
   password: yourPwd,
   host:    yourSmtp,
   ssl:     true
});

最后的一个预备步骤是告知 Passwordless 你选择了哪一种存储接口,并将之初始化:

// Your MongoDB TokenStore
var pathToMongoDb = 'mongodb://localhost/passwordless-simple-mail';
passwordless.init(new MongoStore(pathToMongoDb));

传递令牌

函数 passwordless.addDelivery(deliver) 会添加新的传送机制。每次需要传送令牌时,就会调用deliver。默认情况下,你选择的机制应该按照以下格式为用户提供链接:

http://www.example.com/token={TOKEN}&uid={UID}

deliver 在调用时,需要全部的细节信息。因此,令牌的传递(在本例中,使用的是 emailjs)如下所示,相当简单:

passwordless.addDelivery(
    function(tokenToSend, uidToSend, recipient, callback) {
        var host = 'localhost:3000';
        smtpServer.send({
            text:    'Hello!nAccess your account here: http://'
            + host + '?token=' + tokenToSend + '&uid='
            + encodeURIComponent(uidToSend),
            from:    yourEmail,
            to:      recipient,
            subject: 'Token for ' + host
        }, function(err, message) {
            if(err) {
                console.log(err);
            }
            callback(err);
        });
});

初始化 Express 中间件

app.use(passwordless.sessionSupport());
app.use(passwordless.acceptToken({ successRedirect: '/'}));

函数 sessionSupport() 会使登录状态得到保持,因此,用户在浏览网站时才能一直处于登录状态。请确保你已经提前准备好了会话中间件(比如express-session)。

函数 acceptToken() 会截获任何外来的令牌,验证用户身份,再将他们重定向至正确的页面。尽管 successRedirect 选项不是严格要求的,但笔者强烈建议你使用此选项,从而避免合法令牌通过网站向外的 HTTP 链接的 header 来源泄露出去。

路径选择与身份验证

下文默认你已经通过 var router = express.Router(); 配置好路径选择器。此外,express 文档中也有相应的说明。

你至少需要两个 URLs,从而:

  • 展示用于获取用户邮箱地址的页面
  • 获取表格细节(通过 POST 方法)
/* GET: login screen */
router.get('/login', function(req, res) {
   res.render('login');
});</p>
 
/* POST: login details */
router.post('/sendtoken',
    function(req, res, next) {
        // TODO: Input validation
    },
    // Turn the email address into a user ID
    passwordless.requestToken(
        function(user, delivery, callback) {
            // E.g. if you have a User model:
            User.findUser(email, function(error, user) {
                if(error) {
                    callback(error.toString());
                } else if(user) {
                    // return the user ID to Passwordless
                    callback(null, user.id);
                } else {
                    // If the user couldn’t be found: Create it!
                    // You can also implement a dedicated route
                    // to e.g. capture more user details
                    User.createUser(email, '', '',
                        function(error, user) {
                            if(error) {
                                callback(error.toString());
                            } else {
                                callback(null, user.id);
                            }
                    })
                }
        })
    }),
    function(req, res) {
        // Success! Tell your users that their token is on its way
        res.render('sent');
});

此处有何猫腻?passwordless.requestToken(getUserId) 的任务有二:第一、确保邮箱地址真实存在。第二、将该地址转变为独一无二的用户 ID,通过邮件一并发送,并在之后用于验证用户身份。通常,你都有一套存储用户细节信息的模型,你可以按照上例的说明,进行简单的设置。

在一些情况下(例如,由两个用户编辑过的博客),你可以跳过用户模型,将他们有效的邮箱地址与其各自的 ID 相联系:

var users = [
    { id: 1, email: 'marc@example.com' },
    { id: 2, email: 'alice@example.com' }
];
 
/* POST: login details */
router.post('/sendtoken',
    passwordless.requestToken(
        function(user, delivery, callback) {
            for (var i = users.length - 1; i >= 0; i--) {
                if(users[i].email === user.toLowerCase()) {
                    return callback(null, users[i].id);
                }
            }
            callback(null, null);
        }),
        // Same as above…

HTML 页面

本例只需要一个简单的 HTML 页面,用以获取用户的邮箱地址。默认情况下,Passwordless 会查找 user 输入栏中的内容:

<html>
    <body>
        <h1>Login</h1>
        <form action="/sendtoken" method="POST">
            Email:
            <br /><input name="user" type="text">
            <br /><input type="submit" value="Login">
        </form>
    </body>
</html>

保护网页

Passwordless 提供了能确保只有验证用户才能看到指定页面的中间件:

/* Protect a single page */
router.get('/restricted', passwordless.restricted(),
 function(req, res) {
  // render the secret page
});
 
/* Protect a path with all its children */
router.use('/admin', passwordless.restricted());

谁处于登录状态?

默认情况下,Passwordless 允许通过请求对象 req.user 获取用户 ID。想要展示或重用此 ID,或从数据库中得到更多细节信息,你可以这么实现:

router.get('/admin', passwordless.restricted(),
    function(req, res) {
        res.render('admin', { user: req.user });
});

或者,更一般化地,你可以添加另一个中间件,从模型中抽取出某个用户的全部记录,再将其分配给网站中的任意路径:

app.use(function(req, res, next) {
    if(req.user) {
        User.findById(req.user, function(error, user) {
            res.locals.user = user;
            next();
        });
    } else {
        next();
    }
})

到此为止啦!

以上就是安全地验证用户身份的简单方法。若想了解更多细节,你可以查看深入剖析,从而了解所有的可选项,以及将上述知识整合为一套可行的解决方案的案例。

点评

如前所示,所有的身份验证系统都有其优缺点。你应该按照自己的需求进行合理的选择。基于令牌的验证方式,与绝大多数解决方法(包括经典的用户名/密码方法)一样,都存在一个风险:如果用户的邮箱账户被盗用,并且/或者 SMTP 服务器与用户的连接被入侵,用户在网站的账户就会随之遭到盗用。默认情况下,有两种办法可以减弱该风险(但不是完全避免):短时间有效的令牌以及令牌在使用后自动失效。

对大多数网站而言,基于令牌的身份验证方法代表着走向安全的进步:用户无需再想新的密码(这些密码往往非常简单),也不存在用户重用密码的风险。对于身为开发者的我们,Passwordless 提供了唯一一种(且相当简单的)身份验证解决方案,该方案易于理解,因此容易保护。此外,我们也无需再处理用户的密码了。

另一个值得讨论的点是可用性。我们应该同时考虑两种情况:用户在网站的首次使用以及后续的登录。对首次用户而言,基于令牌的身份验证非常直观:与经典的登录机制一样,他们还是不得不验证邮箱地址。但是,在最佳案例中,除此之外就不需要其他细节信息了。不需要再煞费苦心地想一个符合所有限制条件的密码,也不需要刻意记住密码。如果用户再次登录,其体验与特定的用户案例有关。大多数网站的会话有效期都很长,因而不需要再次登录。或者,用户访问该网站的频率其实非常低,以致于他们想不起来自己是否已经拥有账户,或者忘记了账户密码。在这种情况下,Passwordless 在可用性方面的优势相当明显。同样地,这需要经历不多的几个步骤,解释起来也非常简单。然而,那些用户访问频繁的网站,以及/或那些要求用户一周内手动登录几次的网站(比如 Amazon),经典的身份验证(或更保险地:双重验证)或许更加适合。因为用户很可能会真的记住他们的密码,并且意识到好密码的重要性。

尽管 Passwordless 目前比较稳定,笔者还是非常期待你能在 GitHub 留下评论或一些贡献,或者在 Twitter:@thesumofall 上提问笔者。

原文地址:https://hacks.mozilla.org/2014/10/passwordless-authentication-secure-simple-and-fast-to-deploy/

OneAPM 助您轻松锁定 Node.js 应用性能瓶颈,通过强大的 Trace 记录逐层分析,直至锁定行级问题代码。以用户角度展示系统响应速度,以地域和浏览器维度统计用户使用情况。想阅读更多技术文章,请访问 OneAPM 官方博客

本文转自 OneAPM 官方博客

© 著作权归作者所有

OneAPM蓝海讯通
粉丝 94
博文 631
码字总数 1266889
作品 0
海淀
私信 提问
Web 开发重磅,FIDO 与 W3C 联合支持免密认证登录!

2018 年 4 月 10 号,W3C 官方宣布: FIDO 联盟与 W3C 联合取得 Web 认证标准的里程碑式进展,在全球实现更简单更强大的 Web 认证方式。 在 Google Chrome、Microsoft Edge 和 Mozilla Fire...

局长
2018/04/14
1K
2
沟通CTBS立白集团远程接入成功案例

——解读远程接入平台与生物识别技术的完美结合 近年来,生物识别技术在迅速发展的同时,也加快了普及化的进程,开始广泛应用于企业市场,成为解决企业个人身份认证的重要解决方案之一。而在...

科技探索者
2017/11/21
0
0
沟通CTBS V5.0确保立白集团核心数据万无一失

当前企业信息系统面临的最大挑战是如何能够保证所有固定的、移动的不同设备在任何地方,以任何连接方式安全地、快速地接入企业,使远程和移动用户可以实时获取企业关键应用和信息。如果企业缺...

科技探索者
2017/11/21
0
0
金雅拓凭借最佳多重身份验证解决方案荣膺2016年网络安全卓越奖

数字安全领域的全球领导者金雅拓(Euronext NL0000400653 GTO)日前宣布,荣获2016年网络安全卓越奖。拥有30万名成员LinkedIn信息安全社区将金雅拓SafeNet身份验证服务评为最佳多重身份验证服务...

玄学酱
2018/05/24
0
0
EXCHANGE客户端访问服务器(CAS)中的身份验证方式

在部署完毕exchange后系统会自动建立IIS服务来响应相应的请求。 客户端访问服务器(以下简称CAS)实质上是一台IIS服务器,在服务器中部署一套名为“Default web site”的站点来完成OWA、ecp...

烟台小崔
2018/07/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
15
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部