Node.Js_Express_Part3.Cookie及Session技術介紹與登入登出狀態顯示切換實作
Cookie:是一種儲存在用戶端(瀏覽器)的一小段文字內容,而且用純文字記錄。
作用:為了實踐用戶端跟伺服器端之間狀態保持
通常不建議使用cookie保存敏感訊息,此外用戶自己瀏覽器端還能設定要不要啟用。
- 儲存於客戶端:cookie 存在使用者的瀏覽器中,通常以文字檔案形式儲存。
- 多個網頁狀態共用。
- 有大小限制,包括數量與容量大小。(一般瀏覽器限制每個 cookie 不超過 4KB。)
- 可以設定 cookie 的有效期限,讓它過期後自動刪除。若未設定有效期限,則為 session cookie,會在瀏覽器關閉後自動刪除。(當設定到期日後即便瀏覽器關閉後仍有效,只要沒過期。)
- 安全性考量:cookie 內容容易被第三方攔截和解讀,因此通常需要加密敏感數據或搭配 HttpOnly 和 Secure 屬性來提升安全性。
在 Node.js 中,可以使用 cookie-parser 中介軟體來處理 cookie。
• Chrome:
C:\Users\XXX\AppData\Local\Google\Chrome\User Data\Default\Cookies
Session 是在伺服器端維持的一組狀態資料,通常用來儲存敏感或不希望存放在客戶端的數據。當使用者登入後,伺服器會建立一個 session 並將 session ID 回傳給客戶端(通常透過 cookie 傳遞)。之後的請求中,客戶端帶上這個 session ID,伺服器即可根據 session ID 獲取相應的 session 資料,達到狀態維護的效果。
Session 特點
- 儲存於伺服器端:session 資料儲存在伺服器端,客戶端僅保存 session ID。
- 安全性較高:由於 session 資料存在伺服器端,比 cookie 更安全。
- 依賴 cookie 或 URL 參數:session 通常透過 cookie 或 URL 參數來管理和識別。
- 連線關閉或瀏覽器關閉Session就消失,會過期。
- 每個連線一個獨立的Session。
在 Node.js 中,可以使用 express-session 來管理 session。
對於不太敏感的使用者偏好設定,可能使用 cookie 存放;而對於登入資訊等敏感數據,則適合使用 session 來保護。
Step1.安裝session模組
npm i express-session -S
Step2.導入Session模組
//導入session模組
const session = require('express-session')
Step3.在Express中使用Session的中介軟體,透過app.use
Step4.把私有資料保存在當前請求的session中
app.js程式(增加session保存處理機制)
const express = require('express') //const path = require('path'); const app = express() const bodyParser = require('body-parser') //導入session模組 const session = require('express-session') //註冊session中間件,之後只要可存取到req物件,就必定可存取到req.session。 app.use(session({ secret:'這段是加密的密鑰', resave:false, saveUninitialized:false, }) ) const mysql = require('mysql') const moment = require('moment') //獲取當前時間戳 const conn = mysql.createConnection({ port:3306, host: '127.0.0.1', database: 'blog_db', user: 'root', password: 'root' }) app.set('view engine','ejs') //設置默認採用模板引擎名稱 app.set('views','./views') //設置模板頁面存放路徑 app.use(bodyParser.urlencoded({extended:false})) //將node_modules資料夾,託管為靜態資源目錄 app.use('/node_modules',express.static('./node_modules')) //app.use('/node_modules', express.static(path.join(__dirname, 'node_modules'))); app.get('/', (req,res) => { //使用render函數之前,必須確保已經安裝和配置好ejs模板引擎 res.render('index.ejs',{ user: req.session.user, islogin: req.session.islogin }) }) app.get('/register' , (req,res) => { res.render('./user/register.ejs',{}) }) app.post('/register' , (req,res) => { const body = req.body console.log(body) if(body.username.trim().length <= 0 || body.password.trim().length <=0 || body.nickname.trim().length <=0){ return res.send({msg: '請填寫完整表單欄位,再註冊帳號!',status:501}) } //確認是否帳號名有重複 const sql_str = 'select count(*) as count from blog_users where username=?' conn.query(sql_str,body.username,(err,result) => { console.log(err) console.log('result1:') console.log(result) if(err) return res.send({msg:'帳號名查重異常!',status:502}) if(result[0].count !==0) return res.send({msg:'請改用其他帳號名重新註冊!',status:503}) body.ctime = moment().format('YYYY-MM-DD HH:mm:ss') const sql_str2 = 'insert into blog_users set ?' console.log(body) conn.query(sql_str2 , body , (err,result) => { console.log('result2:') console.log(result) if(err || result.affectedRows !== 1) { console.log(err) return res.send({msg:'註冊新帳號失敗!',status:504}) } res.send({msg:'註冊新帳號成功' , status:200}) }) }) }) app.get('/login' , (req,res) => { res.render('./user/login.ejs',{}) }) app.post('/login' , (req,res) => { const body = req.body const sql_str = 'select * from blog_users where username = ? and password=?' conn.query(sql_str , [body.username,body.password] , (err,result) => { if(err || result.length !== 1) return res.send({msg:'帳號登入失敗' , status:501}) //打印查看目前session內容 console.log(req.session) //將登入成功之後的帳號資訊、狀態結果掛載到session上 console.log(result)//登入成功後的帳號資訊 req.session.user = result[0]//帳號資訊 req.session.islogin = true//狀態結果 res.send({msg:'ok',status:200}) }) }) app.listen(80, () => { console.log('server running at http://127.0.0.1') })
在對默認首頁get請求中調整了傳入參數,採用req.session自定義兩個屬性user跟islogin。
為了之後畫面右上方狀態顯示切換判定依據,可以在index.ejs畫面做參數嵌套判斷。
./views/index.ejs
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap-theme.min.css"> <script src="/node_modules/jquery/dist/jquery.min.js"></script> <!-- 叮嚀:bootstrap的js是有依賴jquery文件的,要在之前先配置jquery。 --> <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script> </head> <body> <!-- 導覽列區塊 --> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">論壇</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <% if(islogin) {%> <div class="nav navbar-nav navbar-right navbar-form"> <button class="btn btn-warning">歡迎</button> <button class="btn btn-danger">註銷</button> </div> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">發表 <span class="caret"></span> </a> <ul class="dropdown-menu"> <li> <a href="#">文章</a> </li> <li> <a href="#">問題</a> </li> </ul> </li> </ul> <% } else {%> <div class="nav navbar-nav navbar-right navbar-form"> <a class="btn btn-success" href="/register">註冊</a> <a class="btn btn-primary" href="/login">登入</a> </div> <% } %> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <h1>貼文列表</h1> <!-- 版權聲明區塊 --> <div class="text-center text-muted"> AAA © BBB 2025 </div> </body> </html>
效果呈現
預設尚未登入只顯示註冊登入
Step5.註銷(登出)
./views/index.ejs(註冊登出事件)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap-theme.min.css"> <script src="/node_modules/jquery/dist/jquery.min.js"></script> <!-- 叮嚀:bootstrap的js是有依賴jquery文件的,要在之前先配置jquery。 --> <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script> </head> <body> <!-- 導覽列區塊 --> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">論壇</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <% if(islogin) {%> <div class="nav navbar-nav navbar-right navbar-form"> <button class="btn btn-warning">歡迎<strong><%=user.nickname%></strong></button> <a class="btn btn-danger" href="/logout">登出</a> </div> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">發表 <span class="caret"></span> </a> <ul class="dropdown-menu"> <li> <a href="#">文章</a> </li> <li> <a href="#">問題</a> </li> </ul> </li> </ul> <% } else {%> <div class="nav navbar-nav navbar-right navbar-form"> <a class="btn btn-success" href="/register">註冊</a> <a class="btn btn-primary" href="/login">登入</a> </div> <% } %> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <h1>貼文列表</h1> <!-- 版權聲明區塊 --> <div class="text-center text-muted"> AAA © BBB 2025 </div> </body> </html>
app.js(增加登出請求)
const express = require('express') //const path = require('path'); const app = express() const bodyParser = require('body-parser') //導入session模組 const session = require('express-session') //註冊session中間件,之後只要可存取到req物件,就必定可存取到req.session。 app.use(session({ secret:'這段是加密的密鑰', resave:false, saveUninitialized:false, }) ) const mysql = require('mysql') const moment = require('moment') //獲取當前時間戳 const conn = mysql.createConnection({ port:3306, host: '127.0.0.1', database: 'blog_db', user: 'root', password: 'root' }) app.set('view engine','ejs') //設置默認採用模板引擎名稱 app.set('views','./views') //設置模板頁面存放路徑 app.use(bodyParser.urlencoded({extended:false})) //將node_modules資料夾,託管為靜態資源目錄 app.use('/node_modules',express.static('./node_modules')) //app.use('/node_modules', express.static(path.join(__dirname, 'node_modules'))); app.get('/', (req,res) => { //使用render函數之前,必須確保已經安裝和配置好ejs模板引擎 res.render('index.ejs',{ user: req.session.user, islogin: req.session.islogin }) }) app.get('/register' , (req,res) => { res.render('./user/register.ejs',{}) }) app.get('/logout', (req,res) =>{ req.session.destroy(function(){ res.redirect('/')//重新跳轉到首頁 }) }) app.post('/register' , (req,res) => { const body = req.body console.log(body) if(body.username.trim().length <= 0 || body.password.trim().length <=0 || body.nickname.trim().length <=0){ return res.send({msg: '請填寫完整表單欄位,再註冊帳號!',status:501}) } //確認是否帳號名有重複 const sql_str = 'select count(*) as count from blog_users where username=?' conn.query(sql_str,body.username,(err,result) => { console.log(err) console.log('result1:') console.log(result) if(err) return res.send({msg:'帳號名查重異常!',status:502}) if(result[0].count !==0) return res.send({msg:'請改用其他帳號名重新註冊!',status:503}) body.ctime = moment().format('YYYY-MM-DD HH:mm:ss') const sql_str2 = 'insert into blog_users set ?' console.log(body) conn.query(sql_str2 , body , (err,result) => { console.log('result2:') console.log(result) if(err || result.affectedRows !== 1) { console.log(err) return res.send({msg:'註冊新帳號失敗!',status:504}) } res.send({msg:'註冊新帳號成功' , status:200}) }) }) }) app.get('/login' , (req,res) => { res.render('./user/login.ejs',{}) }) app.post('/login' , (req,res) => { const body = req.body const sql_str = 'select * from blog_users where username = ? and password=?' conn.query(sql_str , [body.username,body.password] , (err,result) => { if(err || result.length !== 1) return res.send({msg:'帳號登入失敗' , status:501}) //打印查看目前session內容 console.log(req.session) //將登入成功之後的帳號資訊、狀態結果掛載到session上 console.log(result)//登入成功後的帳號資訊 req.session.user = result[0]//帳號資訊 req.session.islogin = true//狀態結果 res.send({msg:'ok',status:200}) }) }) app.listen(80, () => { console.log('server running at http://127.0.0.1') })
留言
張貼留言