上一節(jié)-node.js學(xué)習(xí)(10)—express中間件—body-parser獲取post杈湾、get數(shù)據(jù)
上節(jié)我們講了如何在express中獲取post懂拾、get數(shù)據(jù)析恢,這一節(jié)我們講解cookie和session。
1.給瀏覽器發(fā)送( response ) cookie
我們在server.js添加如下代碼
const express=require('express');
const server=express();
server.use('/test', function (req, res){
if (req.url === '/favicon.ico') {
return
}
res.cookie('user', 'blue'); //發(fā)送cookie給瀏覽器键兜,讓瀏覽器來寫入cookie
res.send('hello cookie');
});
server.listen(8080);
- 在express中凤类,它已經(jīng)為我們封裝好了操作cookie的方法,我們使用
res.cookie
來發(fā)送cookie - 上面代碼表示在
/test
路徑下,設(shè)置cookie為key:user,value:blue
- 如果設(shè)置多個cookie普气,需要寫多行
res.cookie
- 除下設(shè)置key/value谜疤,還可以添加其他參數(shù),如path,http,domain.maxAge,expires等现诀,具體可以看express文檔-res.cookie
res.cookie('user', 'blue',{
path:'/',
httpOnly:false
...
})
啟動服務(wù)夷磕,打開瀏覽器并訪問/test
路徑,我們可以在控制臺找到我們剛設(shè)置的cookie仔沿。
2.服務(wù)器接收瀏覽器傳過來的( request ) cookie
上面我們講了服務(wù)端如何發(fā)送cookie給瀏覽器坐桩,并且也知道瀏覽器訪問服務(wù)器url,會先檢查url下有沒有cookie,有的話在訪問過程中封锉,會將cookie自動添加到請求頭中绵跷,那么服務(wù)器如何從請求頭中獲取cookie呢?
我們修改server.js
const express=require('express');
const cookieParser=require('cookie-parser');
const server=express();
server.use(cookieParser())
server.use('/test', function (req, res){
if (req.url === '/favicon.ico') {
return
}
res.cookie('user', 'blue'); //設(shè)置cookie
res.cookie('user1', 'blue1'); //設(shè)置cookie
console.log(req.cookies) //{"user": "blue","user1":"blue1"}
res.send(req.cookies);
});
server.listen(8080);
- express默認(rèn)需要引入
cookie-parser
模塊(安裝express會自動安裝成福,不用單獨(dú)安裝碾局,只需要引入即可)才能獲取瀏覽器傳過來的cookie。 - 使用
req.cookies
獲取瀏覽器傳過來的cookie,值為對象闷叉。
在瀏覽器中運(yùn)行上面代碼擦俐,如下圖:
- 第一次req.cookies為空,瀏覽器寫入cookie
- 第二次瀏覽器發(fā)送之前寫入的cookie握侧,req.cookies有值
3.cookie簽名(防服務(wù)器數(shù)據(jù)篡改)
何謂篡改蚯瞧,我們都知道,cookie是保存在客戶端的品擎,我們可以通過js或者直接在控制臺修改cookie的埋合,這樣會暴露一個問題:
- 瀏覽器發(fā)起一個修改用戶信息的請求,帶上Cookie信息username=aa(前提已經(jīng)種下cookie)萄传,服務(wù)器接收到請求后甚颂,會去修改username為aa的數(shù)據(jù)
- 如果用戶知道username字段表示用戶名信息,那么他把值改為bb秀菱,然后再次發(fā)起請求振诬,服務(wù)器接收到請求,去修改username為bb用戶的數(shù)據(jù)(假如數(shù)據(jù)庫中剛好有個username為bb的用戶)
- 這樣因?yàn)閏ookie被用戶修改衍菱,導(dǎo)致服務(wù)器數(shù)據(jù)被篡改的風(fēng)險
怎么防止篡改赶么?就有了cookie簽名的由來。
- 假設(shè)現(xiàn)在有個cookie(username=blue),服務(wù)器在發(fā)送cookie給瀏覽器之前先配置一個秘鑰(如sdfjk7777)脊串,通過sha256或者其他加密算法(這里假設(shè)加密方法為secret())辫呻,然后使用
secret('blue','sdfjk7777')
生成一個簽名和cookie值組合(類似{username:s:blue.3byMSHd31XkJ0JszpW7IXndQaUh6XFLxks+6/zWRdXg}
- 其中blue表示真正的值清钥,后面是簽名。假如我現(xiàn)在把blue改成blue1放闺。
- 當(dāng)瀏覽器再次發(fā)送請求時祟昭,服務(wù)器取出簽名(因?yàn)樘L了,這里叫A)以及cookie真正的值(blue1)怖侦。然后使用之前的加密算法判斷secret('blue1','sdfjk7777')==A篡悟,如果不相等,說明cookie被惡意修改了础钠。
4.使用cookie-parser增加cookie簽名
我們修改server.js
const express=require('express');
const cookieParser=require('cookie-parser');
const server=express();
//增加一個隨機(jī)秘鑰
server.use(cookieParser('asdfd45556'))
server.use('/test', function (req, res){
if (req.url === '/favicon.ico') {
return
}
res.cookie('user', 'blue',{
signed:true
});
res.cookie('user1', 'blue1');
console.log(req.cookies); //無簽名cookie {user1:blue1}
console.log(req.signedCookies); //簽名cookie {user:blue}
console.log(req.headers.cookie) //瀏覽器發(fā)過來的cookie: user1=blue1; user=s%3Ablue.3byMSHd31XkJ0JszpW7IXndQaUh6XFLxks%2B6%2FzWRdXg
res.send('ok'); //由于res.send默認(rèn)最后一個生效(相當(dāng)于只能寫一個)恰力,所以上面用console.log代替輸出
});
server.listen(8080);
- 將秘鑰
asdfd45556
傳入cookieParser函數(shù)中叉谜,然后在需要簽名的cookie增加參數(shù)signed:true
- 此時cookie會分割成兩個屬性 cookies-未簽名的旗吁、signedCookies-已簽名的
- 注意:req.signCookies的值為簽名的cookie原始值,不帶簽名字符串停局,閱讀cookie-parser源碼很钓,其將簽名去除然后再賦到signCookies上
- 可以通過req.header.cookie獲取瀏覽器帶過來的cookie
上圖中,s%3Ablue.3byMSHd31XkJ0JszpW7IXndQaUh6XFLxks%2B6%2FzWRdXg為簽名cookie董栽,實(shí)際上是該值是先簽名后encodeURIComponent編碼過來的码倦。我們可以解碼看
console.log(decodeURIComponent('s%3Ablue.3byMSHd31XkJ0JszpW7IXndQaUh6XFLxks%2B6%2FzWRdXg'))
//s:blue.3byMSHd31XkJ0JszpW7IXndQaUh6XFLxks+6/zWRdXg
其中,s:
表示簽名锭碳,blue
為真正的值袁稽,后面的是簽名
所以我們在瀏覽器實(shí)際上仍可以看見用戶信息,cookie簽名并不能起到加密作用擒抛,只是防篡改作用推汽。
5.cookie-session
之前在cookie和session區(qū)別曾講過seesion。下面我們通過nodejs來實(shí)現(xiàn)session的demo(判斷用戶是否來過歧沪,保持用戶登陸狀態(tài))
server.js代碼如下:
const express=require('express');
// const cookieParser=require('cookie-parser');
const cookieSession=require('cookie-session');
const server=express();
// server.use(cookieParser())
server.use(cookieSession({
keys: ['aaa', 'bbb', 'ccc'], //session用來生成簽名的列表,必填
name: 'sessionId', //session名字歹撒,可省略,實(shí)測默認(rèn)名字為'express:sess'
cookie:{ //session配置
//cookie選項(xiàng)
path:'',
...
}
}))
server.get('/test', function (req, res){
if (req.url === '/favicon.ico') {
return
}
console.log(req.session); //第一次為{}诊胞,后面為{hasCome:true}
// console.log(req.cookies);
// console.log(req.signedCookies);
if(!req.session['hasCome']){
req.session['hasCome']=true //寫入hasCome屬性
res.send('歡迎第一次');
}else{
res.send('歡迎再次來');
}
});
server.listen(8080);
- 使用session依賴
cookie-session
中間件暖夭,和cookie-parse不一樣,需要單獨(dú)安裝撵孤,express不帶迈着。 - 該中間件使用必須傳一個key數(shù)組,用來的session進(jìn)行簽名邪码,和cookie簽名一樣裕菠,用來防止篡改,校驗(yàn)霞扬。name可以不傳默認(rèn)為
express:sess
- 使用該中間件,session保存在cookie中糕韧,所以額外配置項(xiàng)就是cookie選項(xiàng)枫振。所以如果expires/maxAge不傳,即N/A萤彩,會話cookie,瀏覽器關(guān)閉即過期粪滤。
- 引入該中間件,req上面會增加session屬性雀扶。demo中第一次進(jìn)來session為空杖小,不存在
hasCome
屬性(判斷是否已經(jīng)來過),所以寫入該屬性,頁面返回歡迎第一次
愚墓,后面再次訪問予权,req.session為{hasCome:true}
,頁面返回歡迎再次來
浪册,可以看下圖req.session
打印狀態(tài)以及頁面展示內(nèi)容
- 我們通過
req.session
可以很方便的訪問session扫腺,由于該session對應(yīng)的id是保存在cookie中,所以我們?nèi)钥梢砸胏ookie-parser在cookie中查看sessionId(注意:雖然sessionId有簽名村象,但是該插件將sessionId存在req.cookies
屬性中笆环,不在req.signedCookies
中,所以通過req.signedCookies
是無法訪問到的)
現(xiàn)在我們把之前server.js的代碼中注釋的cookie-parser等代碼解開注釋厚者,可以看到打印結(jié)果:
console.log(req.cookies)
//結(jié)果為
{
sessionId: 'eyJoYXNDb21lIjp0cnVlfQ==',
'sessionId.sig': 'moOOM66p-CAR9aeTORKkuBYXFk4'
}
console.log(req.signCookies)
//結(jié)果為
{}
- 從session的格式來看躁劣,簽名是單獨(dú)為一個
sessionId.sig
字段,和之前使用cookie-parser簽名一個cookie不一樣(之前直接在cookie上拼接簽名)
- 如果
session
值變化库菲,sessionId
以及簽名sessionId.sig
會跟著變化账忘。我們再換個demo(記錄頁面訪問次數(shù))測試下
更改server.js代碼:
const express=require('express');
const cookieSession=require('cookie-session');
const server=express();
server.use(cookieSession({
keys: ['aaa', 'bbb', 'ccc'],
name: 'sessionId',
}))
server.get('/test', function (req, res){
if (req.url === '/favicon.ico') {
return
}
//count字段表示頁面訪問次數(shù)
if(!req.session['count']){
req.session['count']=1
}else{
req.session['count']++
}
res.send(''+req.session['count'])
});
server.listen(8080);
頁面測試結(jié)果: