項目地址css
用戶能夠隨意篡改 Cookiehtml
通常來講,Session 基於 Cookie 來實現。前端
1.服務器經過 Set-Cookie 頭給客戶端一串字符串vue
2.客戶端每次訪問相同域名的網頁時,必須帶上這段字符串html5
3.客戶端要在一段時間內保存這個Cookie(cookie是保存在客戶端的)node
4.Cookie 默認在用戶關閉頁面後就失效,後臺代碼能夠任意設置 Cookie 的過時時間 大小大概在 4kb 之內react
好比:git
i.當用戶登陸成功,用戶就會獲得一個Cookie好比是sign_email:1@Reagen.com程序員
ii.當用戶訪問首頁的時候,請求中就包含了cookie中的字符串github
iii. 服務器讀Cookie,發現用戶是以sign_email:1@Reagen.com這個信息登陸的,就從數據庫把用戶相關的信息渲染到html中而後再返還給用戶,若是服務器發現讀的cookie是空的,那麼服務器就會返回和以前登陸頁面不一樣的一個頁面
以前說過Cookie是能夠被用戶篡改的,也就是若是用戶篡改了本身的Cookie它就能夠以另外一種身份去登陸或者向服務器發請求了
好比:將頁面中的1030052539@qq.com改成1@Reagen.com後刷新頁面咱們就可獲得保存再數據庫中的用戶1@Reagen.com對應的密碼了,這就是cookie的弊端,用戶能夠修改cookie的id,而後把id對應的密碼明文的展現出來,很危險
怎麼辦?session能夠解決這個問題
看下以前的註冊登陸,server.js中登陸的路由中一旦用戶登陸成功就設置cookie,並將用戶的email重要敏感信息明文的展現在cookie中
+++
if(found){
// Set-Cookie: <cookie-name>=<cookie-value>
response.setHeader('set-Cookie',`sign_in_email = ${email}`)
response.statusCode = 200
}
+++
複製代碼
改一:
咱們不如用一個隨機數記錄用戶的email,setCooke的時候就是展現這個隨機數,這樣隨機數就不容易被篡改了
+++
if (found) {
let sessions = {}
let sessionID = Math.random() * 1000000
sessions[sessionID] = { sign_in_email: email }
response.setHeader('set-Cookie', `sessionID = ${sessionID}`)
response.statusCode = 200
}
+++
複製代碼
這樣登陸成功後,響應頭就會有一個set Cookie和對應的隨機ID,
咱們該如何利用這個隨機的ID呢?
改二:
咱們去看下set Cookie裏面的SessionID對應的值是多少
+++
if (path === '/') {
let string = fs.readFileSync('./index.html', 'utf-8')
// 讀取cookie的email
let cookies = '' // cookies有可能爲空
if (request.headers.cookie) {
cookies = request.headers.cookie.split(';') // ['email = 1@...', 'xxx= 1','yyy=2']
}
let hash = {}
for (let i = 0; i < cookies.length; i++) {
let parts = cookies[i].split('=')
let key = parts[0]
let value = parts[1]
hash[key] = value
}
// 拿到用戶cookie裏面的sessionID,而後經過sessionID去找sessions這個hash對應的對象的sign_in_email屬性對應的值
let email = sessions[hash.sessionID].sign_in_email // 這句話要理解
+++
複製代碼
咱們這樣寫let email = sessions[hash.sessionID].sign_in_email
`會報錯,由於sessions是存在內存裏的,一刷新就沒了,因此要作一下判斷
將let email = sessions[hash.sessionID].sign_in_email
改成 ==>
let sessionId = hash[' sessionID'] // 當心取值時,鍵名有空格
let mySession = sessions[sessionId]
let email
if (mySession) {
email = mySession.sign_in_email
}
複製代碼
.和[]的區別:
sessions[sessionID] = { sign_in_email: email }
複製代碼
和
sessions.sessionID = { sign_in_email: email }
複製代碼
的區別是什麼??
JavaScript中對象屬性有兩種調用方式,一種是點"."一種是"[]"。 通常狀況下咱們使用點調用屬性的方式,可是當obj的某個屬性是一個變量時(你的attr在這裏是一個字符串),這種點調用的方式就行不通了,想一想看obj.'property'這樣的方式不對; 因此,若是對象內的屬性是一個變量,只能使用[]調用
之後能不用.就不用點,而用[]
完成改一和改二處的代碼後,重啓服務器: node server 8888
而後刷新頁面後請求http://localhost:8888/sign_in
而後打開network,sign_in的響應頭裏有set-Cookie: sessionID = 260634.3102729094
是怎麼知道咱們的密碼的呢?
是由於咱們在訪問首頁的時候就帶上了Cookie: sessionID=260634.3102729094
服務器就去看內存裏面這個ID對應的對象的email是多少,因此服務器就知道用戶的身份了,也就是說不告訴瀏覽器email是多少,只告訴這個email對應的ID是多少,這個ID對應的email服務器是有記錄的,就比如是進景區不告訴你票的姓名是什麼,只告訴你票對應的ID是多少,這個票的ID對應的姓名不是你關心的,可是景區是有記錄每一個ID對應的姓名是什麼
就拿去景區買票的例子來講
一個服務器維護了一個哈希叫sessions,它給每一個用戶分配了一小塊內存,這個內存用來存儲用戶的隱私數據,用戶值保存這個key也就是隨機數,爲何是隨機數,由於別人很難猜,就篡改不掉cookie了
Session(不翻譯)---- 什麼是session(session的是指是一塊內存)
1.session是基於cookie實現的session必須將SessionID(隨機數)經過 Cookie 發給客戶端
2.客戶端訪問服務器時,服務器讀取 SessionID
3.服務器有一塊內存(哈希表)保存了全部 session
4.經過 SessionID 咱們能夠獲得對應用戶的隱私信息,如 id、email
5.這塊內存(哈希表)就是服務器上的全部 session
若是不當心手動將這個隨機數刪了,請從新登陸,由於這個隨機數服務器也沒有保存(就比如是把票弄丟了,請從新買票)
編程的經驗:一些看起來很複雜的東西,都是由簡單的數據結構和簡單的交互邏輯實現的,好像不少地方都用到了內存,哈希
JS作的事情:
1.操做dom 2.ajax發請求 3.組織代碼,這些已經被vue,react作了
localStorage是html5提供的API即window.localStorage
localStorage的本質是哈希,Session是服務器上的哈希表,localStorage是瀏覽器上的哈希表
1.任何一個對象({}).toString()
都會變成[object object]
2.只能存字符串,若是存的是對象會自動轉成字符串
3.若是真的想存那個對象怎麼辦呢?只能經過JSON轉成字符串
clear以後localStorage就清空了
咱們在html文件中插入js,在js中寫var a = 1 console.log(a).用瀏覽器在控制檯輸入a= 2的,這時候a就等於2,可是當咱們刷新頁面的時候,這個a是幾?當頁面刷新以後,這個變量就沒了,變成一個新的a ,a = 1.那有什麼辦法可讓變量能記住它上一次的值呢?經過localStorage,它是存在C盤的文件裏的,不是存在瀏覽器上的,因此頁面刷新,那個文件仍是一直存在在那裏
例子: 下面這樣第一次回去localStorage裏找a的值,發現沒有a = 0,而後將a做爲localStorage的key,a的值做爲localStorage裏的key對應的值,下一次每次刷新頁面,local的a的值都會加1
+++
<script>
let a = localStorage.getItem('a')
if(!a){
a = 0
}else{
a = parseInt(a,10)+1
}
console.log('a')
console.log(a)
localStorage.setItem('a',a)
</script>
+++
複製代碼
在有localStorage以前全部的變量在頁面刷新的那一刻所有銷燬不能保存下來,可是有了localStorage能夠存在本地的c盤的一個文件裏以便之後長期的使用,也就是說在localStorage出來以前前端是沒有記憶力的.
例:若是咱們的網站改版了,要提示用戶
alert('您好,咱們的網站改版了,新添了這些功能....')
複製代碼
這樣每次用戶刷新頁面都會彈框提示,會很煩,有沒有辦法只提示一次?
這就須要在跨頁面的時候去記一些東西,怎樣作呢,不要存到變量裏,存到localStorage裏
<script>
let already = localStorage.getItem('已經提示了')
if(!already){
alert('您好,網站已經改版了,多了這些功能...')
localStorage.setItem('已經提示了',true)
}else{
}
</script>
複製代碼
這樣第一次進入頁面的時候,咱們發現沒有提示用戶,因此就提示用戶,當用戶點擊肯定以後,localStorage已經將'已經提示了'的key存下來了,第二次刷新頁面就不會再刷新頁面了,由於咱們知道上一次已經提示過了,這就是localStorage最典型的應用記錄下有沒有提示過用戶
1.LocalStorage 跟 HTTP 無關
2.HTTP 不會帶上 LocalStorage 的值
3.只有相同域名的頁面才能互相讀取 LocalStorage(沒有同源那麼嚴格)
4.每一個域名 localStorage 最大存儲量爲 5Mb 左右(每一個瀏覽器不同)
5.經常使用場景:記錄有沒有提示過用戶(沒有用的信息,不能記錄密碼) LocalStorage 永久有效,除非用戶清理緩存
6.理論上localStorage永久有效,除非用戶手動清理緩存即在瀏覽器按住ctrl+shift+deleate設置裏的高級裏的清除cookie及其餘數據,其餘數據就包括localStorage的數據
若是不是相同域名都能訪問彼此的localStorage那太危險了,若是A網站知道一我的的姓名,郵箱,B網站知道同一我的的出生日期,身份證號,手機號,C網站知道銀行卡的帳號,住址之類的用戶我的信息,只要這3家網站之間存在彼此相互競爭的關係,就不會拿到用戶的完整信息,因此是相對比較安全的
一旦存的數據超過了localStorage的最大容量就會報錯
1,2,3,4,點同localStorage
5.SessionStorage在用戶關閉頁面後就失效
sessionStorage也是window上的一個屬性,API和localStorage同樣
sessionStorage和上面說到的session一點關係都沒有,sessionStorage叫會話存儲而session不翻譯
session能夠不基於cookie實現
拿以前寫的註冊登陸的代碼來講;
當用戶登陸成功後不後臺不響應cookie而是經過JSON直接返回sessionID傳給前端,前端拿到響應後把sessionID後存到localStorage裏面備用,而後跳轉到首頁時候的路徑就不是/,而是把sessionStorage寫到查詢參數裏面,怎樣知道當前用戶的Session呢?經過sessions[query.sessionID],這樣首頁就能夠經過查詢參數來知道當前用戶的sessionID是什麼,這個Session就有用戶的email
// server.js
+++
else if (path === '/sign_in' && method === 'POST') { // 登陸
+++
if (found) {
let sessionID = Math.random() * 1000000
sessions[sessionID] = {sign_in_email: email }
// response.setHeader('set-Cookie', `sessionID=${sessionID}`) 第二種方法基於不依賴Cookie的Session
response.write(`{"sessionID": ${sessionID}}`)
response.statusCode = 200
}
+++
}
+++
複製代碼
// sign_in.html
+++
$.post('/sign_in', hash)
.then((response) => {
// window.location.href = '/' 第二種方法基於不依賴Cookie的Session
let obj = JSON.parse(response)
localStorage.setItem('sessionID',obj.sessionID)
window.location.href = `/?sessionID=${obj.sessionID}` // 必定要當心這裏的href地址不能有空格否則就找不到這個sessionID
}, (request) => {
alert('郵箱與密碼不匹配')
})
+++
複製代碼
// server.js
+++
if (path === '/') {
let string = fs.readFileSync('./index.html', 'utf-8')
/* 註釋的部分爲經過cookie的方法拿到用戶的email和password
let cookies = '' // cookies有可能爲空
if (request.headers.cookie) {
cookies = request.headers.cookie.split(';') // ['email = 1@...', 'xxx= 1','yyy=2']
}
let hash = {}
for (let i = 0; i < cookies.length; i++) {
let parts = cookies[i].split('=')
let key = parts[0]
let value = parts[1]
hash[key] = value
}
let sessionId = hash[' sessionID']
let mySession = sessions[sessionId]
*/
// 下面的方法是首頁經過前端發來的查詢參數的sessionID去sessions裏面讀哪一個session,這個session就有用戶的郵箱
let mySession = sessions[query.sessionID]
let email
if (mySession) {
email = mySession.sign_in_email
}
+++
複製代碼
以上註釋的部分爲以前是經過基於cookie的Session
Session大部分狀況是基於Cookie來存sessionID的,可是也能夠經過查詢參數和localStorage來存儲sessionID
是有歷史緣由的,localStorage是新的API,那麼以前的程序員是如何作跨頁面的存儲呢?只能經過cookie, cookie也是存在c盤的一個文件裏的,因此Cookie,以後出現了localStorage就不用cookie了,cookie的一個問題就是全部保存在cookie上的數據都會被保存到服務器上 ,這就讓請求變慢,服務器性能降低
前端永遠不要讀寫Cookie,那是後端作的事,Cookie通常只存一個sessionID,不存用戶的信息,若是想知道用戶的數據,向服務器請求
也就是日常說的web性能優化,說的就是如何加速http的請求和響應
那就向以前寫的註冊登陸的項目裏,添加css/default.css和js/main.js,而後在server.js裏給他們加路由
而後讓default.css和main.js的內容很大很大,發現它們請求的時間很長,那麼如何讓它們請求的事件加快呢?
在響應的文件路由裏添加
response.serHeader('Cache-Control','max-age=30')
複製代碼
在控制檯看響應文件的響應頭多了'Cache-Control','max-age=30',表示30秒以內就不要再次向服務器發起請求了,當前的已是最新的了.因此該文件的請求時間很短,基本上是0ms.30秒以後再次刷新頁面請求的時候,又會從新發起請求可是響應頭又多了一個'Cache-Control','max-age=30'再接下來的30秒內不會再向服務器發請求......如此往復
爲何不少網站首頁不容許在響應頭裏設置Cache-Control?(通常html不設置緩存)
由於若是頁面更新了,用戶無法獲取最新的版本,由於在緩存時間內,用戶使用的都是舊的緩存
在設置的緩存時間內加載文件的時候不會再向服務器發請求.那怎麼辦呢?若是在設置的緩存時間內,服務器更新了這個文件呢?只有請求相同的URL纔會利用以前的緩存,若是文件更新了只需在以前的請求路徑下加上一個查詢參數就好了
如: <link rel="stylesheet"href="./css/default.css?v=2">
這樣有什麼用呢?還不是在訪問這個css,可是URL變了,多了個查詢參數,這時候就會重新向服務器發請求,由於URL不是以前的URL了,不是以前的URL就不用以前的緩存了,這樣每次升級更新文件的時候,再引入文件的地方修改請求的URL讓每次的查詢參數不同就能夠了.當咱們改了請求的URl時,第一次打開頁面會從新發起請求,可是第二次再次打開的時候又會回到緩存控制的狀態,繼續不向服務器發請求
如:<link rel="stylesheet" href="./css/default.css?v=3">
因此實際中通常'Cache-Control','max-age=xxx'的時間有多長就設置多長,若是文件更新了,只須要修改文件請求時候的URL便可
如:<link rel="stylesheet" href="./css/default.css?v=n">
這樣既能保證緩存的事件保持好久,又能隨時升級更新文件
和Cache-Control很像的也是用來控制緩存的,若是同時使用Cache-Control和Expires就會使用Cache-Control,由於Cache-Control是後面纔出來的,之前用Expires,如今用Cache-Control
語法:
Expires: <http-date> // 這裏的時間是GMT時間
複製代碼
當前的GMT時間
d = new Date()
Wed Jul 31 2019 11:49:12 GMT+0800 (中國標準時間)
d.toGMTString()
"Wed, 31 Jul 2019 03:49:12 GMT"
複製代碼
也就是說Expires是設置什麼時間過時(設置時間點),Cache-Control(設置時間點)設置的是多長時間後過時
爲何儘可能別用Expires呢,由於它的事件是基於本地時間,若是用戶本地事件錯亂了,那麼緩存控制的事件也就還不肯定了
若是Expires和Cache-Control同時設置,那麼優先使用Cache-Control,由於Cache-Control是新的API
先說一下MD5,MD5是一個摘要算法,什麼是摘要算法
現實中咱們下載一個文件,咱們怎麼知道本身有沒有下載對呢?會不會中間除了什麼錯,致使少了幾個字節呢?MD5就是爲了解決這個情景生的
好比下載Linux
Linux 400MB MD5值: xxxx
本身下載的400MB MD5值: yyyy
理論上2個MD5值要同樣才表示下載的Linux是完整的
好比: 在桌面上建立了一個1.txt的文件,文件內容爲1111111111111,而後在終端中運行md5sum 1.txt
(在Linux系統中)就會顯示 f85ed079926039709c8f506a1866c2a9 這樣的一串MD5值
咱們再在桌面賦值一份1.txt,文件名叫1-copy.txt,而後進去刪掉文件內容的一個1,而後在終端中運行md5sum 1-copy.txt
就會顯示MD5的值爲87b8769b874865e65a4525bfe9e56ba8
先後比較2次的MD5值是不同的因此就意味着下載的文件不是完整的了
MD5和http有什麼關係嗎?ETag
nodejs也有一個MD5的庫,這個庫沒用過,就用CRM的套路,Google nodejs md5,就會出現npm有一個md5的庫
首先安裝一下:npm install md5
引入:
var md5 = require('md5');
複製代碼
而後在server.js(後端文件)中須要算MD5的路由上,而後把文件的MD5即fileMD5放到ETag上
+++
if (path === '/js/main.js') {
let string = fs.readFileSync('/js/main.js', 'utf-8')
let fileMD5 = md5(string)
response.setHeader('ETag', fileMD5)
複製代碼
當加載main.js的時候,文件的響應頭裏面就多一個ETag:MD5的值
但是這有什麼用呢?當咱們刷新頁面的時候,請求頭裏面多了一個
if-None-Match: ETag上那個MD5的值
複製代碼
也就是說他把上一次的MD5值放到下一次請求的if-None-Match裏面,if-None-Match裏面的MD5值就至關於一個版本號,若是服務器收到這個版本號若是和服務器上的MD5值同樣說明不須要下載
+++
if (path === '/js/main.js') {
let string = fs.readFileSync('/js/main.js', 'utf-8')
let fileMD5 = md5(string)
response.setHeader('ETag', fileMD5)
if(request.headers['if-None-Match'] === fileMD5){ // 若是同樣說明不要從新下載
response.statusCode = 304 // 沒有響應體只有響應頭
}else{
response.setHeader('ETag', fileMD5)
response.write(string) // 有響應體
}
response.end()
+++
複製代碼
304表示not Modified,表示沒改過
那麼使用ETag和使用Cache-Control有什麼區別嗎?
Cache-Control:表示在緩存時間內若是再次加載相同URL的文件就直接不發起http請求到服務器,從瀏覽器緩存中拿數據
ETag: 第一次加載文件的時候,會把MD的值放到響應頭的ETag裏面,當第二次加載文件的時候,頁面發起請求時請求頭多一個if-None-Match它的值是上一次的MD5的值.服務器收到請求後會和上一次的MD5值進行比較若是同樣就沒有響應體,給個304
一句話總結區別就是,Cache-Control不發起請求,ETag發請求可是響應體是空的(都是針對請求同上一次同樣的文件)