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