摘要:在路由回調函數里面操作的時候,直接用就可以獲取到客戶端的值。用回調函數來寫后期看起來會很吃力看有沒有重名的看是不是同一郵箱又想重復注冊如果是以上兩種情況,就發送錯誤信息。此賬戶名已經被注冊。
1. 開場白
用戶系統是許多網站的基礎。這篇文章主要就是講解如何寫一個基于Node的單頁應用的用戶系統,這個用戶系統的功能包括:注冊,登錄,自動登錄,忘記密碼,修改密碼,郵件激活。
如果使用在后端使用模板引擎,而不是用前后端分離的方案,用戶系統貌似沒有那么復雜。在這個Nodejs教程里面已經介紹得很詳細了(這是個不錯的Nodejs教程)。但是如果選擇前后端分離的方案,比如像接下來要介紹的SPA,那用戶系統又該怎么處理呢?模板引擎的方案里面,事實上session/cookie上都做了封裝,所以操作起來相對簡單。但后者則不一樣,它需要我們對于HTTP相關的概念有更加清晰的認識。要求會更加細致。
下面先介紹一下一些基礎的知識。說得不會很多,但是對于徹底理解Cookie,Session整個Authentication的機制非常重要。
2.1 HTTP 2.1.1 Cookie & Session眾所周知,HTTP是無狀態的協議。這個的意思就是說,如果發送兩個完全一樣的請求,那么收到的響應也會完全相同。然而在實際生活中,這明顯不符合許多場景。因為每個人雖然都點擊了按鈕,但我是Harry,她是Clara,我們應該收到不同的內容。服務器需要對我們做出區分,這時候cookie就登場了。我發出請求,服務器在響應里面加一個Set-Cookie,到我們瀏覽器里設了一個cookie(點開devtool->Application->Cookies查看),下一次發送請求的時候,我的header里面就帶有cookie了,服務器看到cookie,就知道我是Harry了。這樣就完成了一次認證。
但是接下來還有一個問題:服務器資源極其寶貴,如果每次都認證會造成資源浪費。加之,如果我希望能夠暫時性地在當前會話存儲一些信息,存儲在cookie會顯得非常浪費。因此session就來了。
session就是當前用戶的回話信息。它需要用到cookie,但不需要把所有信息都放在cookie里面,它需要的只是一個標示。
session的信息是存儲在服務器上的,可以存在緩存里,數據庫里或者類似Redis之類的東西里(沒用過..)。舉個例子,Express-session里面的session的標示是一個名字為connect.sid的cookie。這個cookie是隨機生成的獨一無二的序列碼,每次用戶發起請求的時候,cookie跟著到了服務器上去。服務器檢查一下用戶的connect.sid,然后從內存,緩存,數據庫或者Redis里面找到相應的信息,然后通過中間件進一步加到請求里面。這樣服務器就可以使用專屬于這個用戶的信息而不再需要多次驗證了。
因此cookie是整個用戶機制的核心,下面簡單介紹一下相關的header。
Set-Cookie是request的header。header的格式是NAME=VALUE然后用分號‘;’分隔開來。
其中有幾個設置比較常用:
expires=Date (設置cookie的到期時間)
secure (僅僅只在https下使用)
HttpOnly (使得cookie不能被客戶端JavaScript修改)
maxAge (cookie的保持時間,以毫秒為單位)
2.2 Node.js 關于cookie讀取和設置cookie在Nodejs里面都很方便,在Express里面添加中間件cookie-parser,可以把cookie對象直接賦給req。在路由回調函數里面操作的時候,直接用req.cookie就可以獲取到客戶端的cookie值。
而設置客戶端的cookie則需要用res.cookie函數來設置:
// 把cookie里面的name值設為name res.cookie("name", name, { maxAge: 1000 * 60 * 60 * 24 * 30, path:"/", httpOnly: false })session機制
Express的session實現需要一個中間件:
var session = require("express-session") app.use(session({ secret: settings.cookieSecret, // 設置密碼“種子” store: new MongoStore({ url: "mongodb://localhost/color" // 這里用了數據庫存儲session,如果不設置就會用內存 }), resave: true, saveUninitialized: true }))
有關session的使用Nodejs教程里面有介紹,具體來說,比如用戶登錄之后,可以設置 req.session.user = "harry", 然后之后的所有需要用到用戶登錄的場景都可以先判斷一下req.session里面有沒有user這一項。這樣就完成了一次區分,而不需要再次驗證。
2.3 前端在這里的預設是要做一個單頁應用。如果使用模板引擎,使用render很容易就可以完成登錄等等的功能,但如果要寫一個前后端分離的應用,比如一個SPA,那就不得不使用AJAX來收發用戶信息。
不管使用什么庫來收發AJAX,有一點是需要注意的:那就是發送的AJAX請求要包含credentials: "include" 以保證cookie能夠被攜帶發送到后端,否則后端的req.cookie不會收到。
對于需要確認用戶已經登錄了才能夠使用的路由,需要加一個中間件。這個中間件的作用是檢查req.session.user是不是已經定義了。一般來說,在用戶登錄之后都需要設置一下req.session.user,以表示處于登錄的狀態。
function authorize(req, res, next) { if(req.session.user) { next() } else { res.status(401).send({errorMsg: "Unauthorize"}) } }3.2 注冊
對于一個注冊的過程來說需要有如下的一些步驟。收到用戶的用戶名,郵箱之后,要在數據庫里面找一下,如果找到了同名或者用郵箱的,就要告知用戶,重名了。如果沒有重名,就發送郵件到郵箱中進行驗證,同時創建一個未激活的賬戶。
另一個要注意的點就是密碼的存取最好不要直接存入,推薦是先加密。
這里涉及到了多重嵌套的異步,可以使用我之前寫的這篇文章的co,也可以用async/await。用回調函數來寫后期看起來會很吃力...
function *registerGen(req, res, newUser) { try { // 看有沒有重名的 const userOfSameName = yield new Promise(function(resolve, reject) { User.get("NAME", req.body.name, function(err, user) { if(err) reject(err) resolve(user) }) }) // 看是不是同一郵箱又想重復注冊 const userOfSameEmail = yield new Promise(function(resolve, reject) { User.get("EMAIL", req.body.email, function(err, user) { if(err) reject(err) resolve(user) }) }) // 如果是以上兩種情況,就發送錯誤信息。 if(userOfSameName) { return res.status(200).send({ errorMsg: "此賬戶名已經被注冊。"}) } else if (userOfSameEmail) { return res.status(200).send({ errorMsg: "此郵箱已經被注冊。"}) } // 成功的話就新建一個未激活的賬戶 yield new Promise(function(resolve, reject) { newUser.save(function(err, user) { if(err) { console.log("Register error:" ,err) reject(err) } resolve(user) }) }) // 發送激活郵件 yield new Promise(function(resolve, reject) { const nameHash = crypto.createHmac("sha256", SECRET) .update(req.body.name) .digest("hex") const emailHash = crypto.createHmac("sha256", SECRET) .update(req.body.email) .digest("hex") const base = "http://colors.harryfyodor.tk/activate/" // 打開這一段鏈接之后會可以通過立即發起一個ajax來更新數據庫,激活賬戶。 const link = `${base}${req.body.name}/${nameHash}|${emailHash}` User.activate({ subject: "Colors 驗證郵件", html: "如果您并沒有注冊Colors,請忽略此郵件。點擊下面鏈接激活賬戶。3.3 登陸
激活鏈接", to: req.body.email }, function(err) { if(err) reject(err) res.send({ ok: true }) resolve() }) }) } catch(e) { // 如果有錯誤就在這里發起,方便debug return res.status(500).send({ msg: "ERROR"}) console.log("Error ", e) } } function register(req, res) { // 密碼需要先加密,不推薦明文存儲。 var md5 = crypto.createHash("md5"), password = md5.update(req.body.password).digest("hex"); // 創建用戶,這里的User是model(后端MVC的M)的一個構造函數。 var newUser = new User({ name: req.body.name, password: password, email: req.body.email }) // 用co函數來實現同步寫法寫異步 co(registerGen(req, res, newUser)) }
用戶登錄需要有以下的步驟,代碼就不詳細敘述了。這里面需要非常繁瑣的判斷語句,但是理解起來非常簡單。
激活用戶需要用到nodemailer這個庫,非常方便,用起來也非常簡單。可以上官網看。如果使用163郵箱作為發件的郵箱,有一點要格外注意,那就是密碼處要是網易的授權密碼。這一個需要在163郵箱里面自己設置,然后代碼里就用那一個授權密碼。這一點需要格外注意。
function sendEmail(detail, callback) { var config_email = { host: "smtp.163.com", post: "25", auth: { user: "example@163.com", pass: "**********" // 這個密碼不是郵箱密碼,請先到郵箱里面設置授權密碼。 } } var transporter = nodemailer.createTransport(config_email) var data = { from: config_email.auth.user, to: detail.to, subject: detail.subject, html: detail.html } // 異步發送郵件 transporter.sendMail(data, function(err, info) { if(err) { console.log("SendEmail Error", err) callback(err) } else { console.log("Message sent:" + info.response) callback(null); } }) }4.總結
當然,這一個用戶登錄系統仍然還有很多要改進的地方(比如安全問題等等)。除此之外,在功能上還有不少需要增加的。比如修改密碼,比如更換密碼等等,看了上面的內容,其實要完成這些功能也是非常簡單的一件事了。
如果感興趣的話可以看看我自己寫的一個網站,Colors,這是一個基于React和Nodejs的網站,有完整的用戶系統,如果沒有什么頭緒的話可以參考一下~
如果文章中有什么錯誤或者不妥的地方,歡迎指出,互相交流學習~感謝閱讀~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91125.html
摘要:獲取獲取上下文句柄執行計算銷毀句柄除此之外,還可以使用意為在瀏覽器環境執行腳本,可傳入第二個參數作為句柄,而則針對選中的一個元素執行操作。 我們日常使用瀏覽器或者說是有頭瀏覽器時的步驟為:啟動瀏覽器、打開一個網頁、進行交互。 無頭瀏覽器指的是我們使用腳本來執行以上過程的瀏覽器,能模擬真實的瀏覽器使用場景。 有了無頭瀏覽器,我們就能做包括但不限于以下事情: 對網頁進行截圖保存為圖片或 ...
摘要:很多同學肯定都想過服務端渲染的問題。然而一看關于服務端渲染的文檔,可能就被唬住了。啪啪啪,啪啪啪好,然后就好了,不到行的代碼,我們就實現了一個通用化的服務化的單頁應用服務端渲染解決方案。 前端發展到現在,SPA應該已經被應用的非常廣了。可惜的是,我們前進的是快,而人家搜索引擎爬蟲跟用戶的瀏覽器設備還跟不上腳步。辛辛苦苦寫好的單頁應用,結果到了SEO跟瀏覽器兼容這一步懵逼了。 很多同學肯...
摘要:介紹微信風格的,與客戶端體驗一致,這個自己去微信上看吧,略。微信調試一件套,網頁授權模擬集成代理遠程調試。這些在微信開發者中心有介紹,略。年微信開發經驗的人,終于又成為了零年開發經驗的人,重新走上了踩坑之路。 showImg(https://segmentfault.com/img/bVtEd1);活動地址:http://fequan.com/2016/ 注意:英文不好,小記也帶有自己...
摘要:在年成為最大贏家,贏得了實現的風暴之戰。和他的競爭者位列第二沒有前端開發者可以忽視和它的生態系統。他的殺手級特性是探測功能,通過檢查任何用戶的功能,以直觀的方式讓開發人員檢查所有端點。 2016 JavaScript 后起之秀 本文轉載自:眾成翻譯譯者:zxhycxq鏈接:http://www.zcfy.cc/article/2410原文:https://risingstars2016...
摘要:前端每周清單第期現狀分析與優化策略單元測試爬蟲作者王下邀月熊編輯徐川前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清單第 29 期:Web 現狀分析與優化策略...
閱讀 3553·2021-11-25 09:43
閱讀 3135·2021-10-08 10:04
閱讀 1625·2019-08-26 12:20
閱讀 2053·2019-08-26 12:09
閱讀 595·2019-08-23 18:25
閱讀 3573·2019-08-23 17:54
閱讀 2322·2019-08-23 17:50
閱讀 803·2019-08-23 14:33