无密码身份验证:安全、简单且部署快速
Passwordless authentication: Secure, simple, and fast to deploy
【編者按】本文作者為 Florian Heinemann 與 Robert 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 方法)
此處有何貓膩?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/passwo...
OneAPM 助您輕松鎖定 Node.js 應用性能瓶頸,通過強大的 Trace 記錄逐層分析,直至鎖定行級問題代碼。以用戶角度展示系統響應速度,以地域和瀏覽器維度統計用戶使用情況。想技術文章,請訪問 OneAPM 官方博客。
本文轉自 OneAPM 官方博客
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的无密码身份验证:安全、简单且部署快速的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS开发之简单画板实现
- 下一篇: 张涵20160401作业