在開發一個網站時,絕大多數數狀況都會用到登陸、自動登陸功能。本篇是第一篇,用cookie和session來記錄用戶數據。html
你們都知道http協議是無狀態的,也就是,從這個頁面到另外一個頁面,服務端是不知道是否爲同一用戶的。因此,慢慢發展成了前端用cookie保存sessionID,後端使用session進行加密及解密的一套方法。前端
在這裏我稍微講一下cookie和session是如何合做的,咱們前端人員剛接觸node開發時,思惟多是須要進行轉化的,可能轉化須要一段時間。可是當你搞明白了之後,成爲全棧工程師後,就會對整個流程瞭然於胸了。這裏使用express來說解vue
首先,明確要記錄用戶,cookie裏應該要存什麼。注意,cookie裏儘可能不要存儲密碼和用戶名,一是有被竊取的危險,而是增長cookie的大小。(固然並非說必定不能這麼作,好比把用戶名和密碼進行md5等方式處理,也相對會很安全)cookie裏存儲的是sessionID,那這個sessionID是如何跟服務端進行交互的,後面詳細講。node
用戶的請求過來之後,若是在請求體中沒有cookie,則建立一個新的session,而且建立一個不會重複的sessionID, 把seesionID和session在store裏進行關聯。(store默認是內存)ios
MemoryStore.prototype.set = function set(sessionId, session, callback) {
this.sessions[sessionId] = JSON.stringify(session)
callback && defer(callback)
}
複製代碼
當setCookie的時候,把註冊session中間件中的option進行處理:vue-router
具體是把option中的name, secret跟生成的sessionID進行簽名加密,設置res.setHeader('Set-Cookie', header) 把加密後的結果傳到客戶端。數據庫
這時,cookie就會隨請求頭到了服務端, 那麼express-session就會對cookie進行解密,把sessionID解出來,而後經過這個id,就找到了store中存儲的session,也就拿到session中的用戶數據,好比username等(前提是在登陸時,把username存到了session上面,好比req.ression.username = user)。express
這時,服務端就知道訪問者是誰了,就能夠把屬於這個訪問者的數據返回到頁面中。json
上面講解了總體流程,雖然沒有展開講,但我理解能夠幫助你們抓到總體脈絡。axios
好了,下面開始展開講代碼。具體的各個key是什麼含義,我就不講了,具體可看文檔。
express-session是個中間件,那麼就須要把這個中間件加載到express程序中。
const session = require('express-session');
app.use(session({
secret: 'test-node-session',
resave: false,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: false,
// 若是不設置maxAge,則退出關閉瀏覽器tab,當前cookie就會過時
//maxAge: 1000 * 60
}
}));
複製代碼
經過上面設置,當一個請求來了之後,若是以前頁面沒有對應的cookie,則產生一個新的cookie,若是有,會進行更新後從新發送到瀏覽器中。
固然,頁面請求後就會產生這個name爲connect.sid的cookie。 這時咱們進行登陸:
登陸時,經過post請求,把數據和cookie都給到了服務端。
app.post('/author/gologin', (req, res) => {
// 經過req.body拿到用戶名和密碼、是否自動登陸
...
// 去數據庫裏進行匹配,若是匹配到,說明登陸成功
....
// 這時,能夠把用戶名存儲到session中,
req.session.user = req.body.username;
// 由於咱們是單頁應用,因此返回json,讓前端路由到對應的頁面
res.json({
code: 0,
mesg: ''
});
})
複製代碼
前端頁面拿到請求的返回值,決定路由到哪一個頁面。
// 使用vue
axios.post('/author/gologin', {
data: JSON.stringify(this.accountFormItem)
})
.then(res => {
console.log('登陸帳號返回數據:', JSON.stringify(res));
switch (+res.data.code) {
case 0:
this.$router.push('/go/to/path/list');
break;
case 2004:
this.inputError = true;
break;
default:
break;
}
})
.catch(err => {
console.error('登陸帳號出現錯誤:', err);
});
複製代碼
這時,vue-router會進行導航,到了對應的頁面。 當到了新的頁面後,好比到了列表頁,那就會涉及去訪問列表的異步接口,
這裏注意,應該在全部的異步請求中去判斷一下用戶是否存在,也就是先查一下用戶是否已經登陸。好比客戶端發送了一個get請求 '/get/total/list', 到了服務端,進行處理:
// node進行處理
app.get('/get/total/list', (req, res) => {
if (!req.session.user) {
return res.status(401).send({msg: 'Unauthorize'});
}
// 去數據庫裏查數據
...
// 把數據返回給客戶端
return res.json({code: 0, data: result});
})
複製代碼
在上面的代碼中,有很重要的一句話,那就是若有找到req.session.user,則繼續流程,若是沒有找到,就返回401狀態,也就是未認證,這個狀態是符合http語義的。
那麼服務端返回這個狀態,客戶端須要拿到而且路由到登陸頁。這種狀況通常不會出現,可是要處理,好比說有人更改了cookie的時候,就匹配不到用戶數據了。
// 客戶端使用axios,能夠對數據的返回進行攔截,在單頁的app.js頁面
axios.interceptors.response.use(response => {
// 查看返回的狀態
return response
},
error => {
if(error.response.status === 401) {
router.push('/login');
}
}
);
複製代碼
這時頁面回到了登陸頁,要求用戶從新登陸。
上面就是整個過程了。固然,上面仍是個比較粗略的實現,沒有包含用戶直接刷新頁面的狀況。下面稍微提一下。
好比當用戶直接刷新列表頁,咱們知道,單頁應用只有一個html入口文件,好比默認是index.html,那麼過程就是須要把index.html返回到瀏覽器,瀏覽器根據路由渲染對應的頁面,而後進行頁面內容的異步請求。(具體如何作到,請看我對單頁應用在node中運行起來的相關文章)
這裏有個體驗問題: 若是用戶在這時把cookie刪除了,那麼頁面會先進行上面說的步驟,最後才返回401,而後再跳轉到登陸頁,會有一個很明顯的閃爍過程,先把列表頁加載了出來,又跳到了登陸頁,用戶體驗很差。
那麼咱們能夠作些優化。
// vue 全局路由守衛
router.beforeEach((to, from, next) => {
function getRouteEach(to, next) {
if (sessionStorage.getItem('testuser')) {
// 若是url是登陸頁,則路由到內容頁
if (to.path === '/login') {
return next({path: '/to/the/path/list'});
}
next();
} else {
// 若是發現沒有登陸,則去請求一次登陸狀態
axios.get('/api/getsid')
.then(res => {
// 服務端有登陸狀態
if (res.data.code === 0) {
sessionStorage.setItem('testuser', res.data.data.sessionName);
// 路由到對應頁面
return getRouteEach(to, next);
} else if (to.path !== '/login') {
// 若是確認沒有登陸,則跳到登陸頁面
return next({path:'/login'});
}
next();
})
.catch(e => {
console.error('查看sid失敗:', e);
})
}
}
getRouteEach(to, next);
});
複製代碼
你們都知道,很長一段時間內,你們都用這種方式來處理,可是這裏有個問題,就是在防csrf攻擊(跨站請求僞造)的時候,就容易防不住了。固然能夠用好比雙cookie認證等方式增強,可是下一篇我要講的token方式更爲方便。<<從單頁應用看node的token>> 歡迎你們繼續關注~~