在本文中,我們将了解後端應用程式中使用的基于會話和令牌的身份驗證方法。
基于會話的身份驗證
簡單講,基于會話(Session)的身份驗證使用存儲在裝置上的特殊代碼(Session ID)來記住你通路網站時的身份,保持登入狀态并記住你的資訊,直到你離開或登出。
還沒明白嗎?别着急,我們一步一步來看。
1. 使用者登入:
使用者通過特殊請求通過郵箱和密碼發送到伺服器來登入。
2. 檢查詳情:
伺服器檢查提供的詳細資訊是否與為使用者存儲的資訊比對。
3. 建立會話:
如果一切正确,伺服器将建立一個儲存使用者資訊(如User ID、權限和時間限制)的“Session”。此資訊安全地儲存在伺服器的存儲中,名字可以使用諸如express-session。
4. 擷取會話ID:
伺服器将此“會話 ID”發送回使用者的裝置,通常作為響應中的Cookie。
5. 使用會話 ID:
每當使用者想要從伺服器擷取某些内容時,他們的裝置會自動在其請求中包含此會話 ID。
6. 伺服器檢查:
伺服器使用此會話 ID 來查找會話存儲中存儲的有關會話使用者的資訊。
以下是express-session工作原理:
- 當使用者登入時,伺服器會為該使用者建立一個會話,并在包含會話 ID 的響應中設定一個 cookie。
- 浏覽器會自動在向伺服器發出的後續請求中包含此會話 ID cookie。
- 當伺服器收到請求時,express-session 中間件使用 cookie 中的會話 ID 來檢索相關會話資料。
- 存儲在req.session中的資料(例如 userId)可用于處理請求。
7. 授予通路權限:
如果一切全都比對,伺服器就知道使用者是真實的,并響應他們所請求的内容。
代碼例子
下面是一個使用 Express.js 實作會話身份驗證的 Node.js 應用程式的示例。
const express = require('express');
const session = require('express-session');
const app = express();
// 中間件設定
app.use(session({
secret: 'your_secret_key',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Set the cookie as HTTP-only, Optional
maxAge: 60*30 // In secs, Optional
}
}));
//登入
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
req.session.userId = user.id; // Store user ID in session
res.send('Login successful');
} else {
res.status(401).send('Invalid credentials');
}
});
//受保護頁面
app.get('/home', (req, res) => {
if (req.session.userId) {
// User is authenticated
res.send(`歡迎您光臨,${req.session.userId}!`);
} else {
// User is not authenticated
res.status(401).send('Unauthorized');
}
});
//登出
app.get('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
res.status(500).send('登出出現錯誤');
} else {
res.redirect('/'); // Redirect to the home page after logout
}
});
});
基于令牌的身份驗證
JWT 身份驗證使用包含使用者資訊的數字簽名令牌(Token),其允許對網站或應用程式進行安全且經過驗證的通路,而無需重複登入。
讓我們看一下基于令牌的身份驗證的分步工作流程。
1. 使用者登入請求:
使用者通過特定請求将電子郵件和密碼發送到伺服器來登入。
2. 憑證驗證:
伺服器根據存儲的使用者資料驗證提供的憑據。
3. 代币生成:
驗證成功後,伺服器會建立一個令牌(通常為 JWT - JSON Web 令牌)。該令牌儲存使用者資訊(聲明),例如 user_id、權限。
4. 令牌簽名與哈希:
該令牌使用密鑰進行簽名,并使用雜湊演算法(如 SHA256)進行處理以建立散列。
5. 發送令牌:
伺服器将此令牌發送到用戶端,用戶端通常将其存儲在浏覽器中。
6. 代币存儲選項:
用戶端可以以不同的方式存儲令牌,例如 HttpOnly Cookie、會話存儲或本地存儲。建議存儲在 HttpOnly Cookies 中,因為它可以防止 JavaScript 通路,進而增強針對 XSS 攻擊的安全性。
7. 令牌到期和安全:
令牌通常有一個過期時間以增強安全性。
8. 在請求中包含 Token:
對于向伺服器發出的每個請求,用戶端都會在授權标頭中發送令牌。
最好在令牌前加上“Bearer”字首。
axios.get(URL, {
headers: {
'Authorization': 'Bearer ' + token,
},
})
9. 伺服器端驗證:
收到請求後,伺服器檢索令牌。
10. 令牌驗證和使用者認證:
使用密鑰,伺服器驗證令牌并從中提取聲明。如果聲明中的使用者資訊存在于伺服器的使用者表中,則伺服器會對使用者進行身份驗證,并授予對所請求資源的通路權限。
代碼例子
//登入
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
jwt.sign({ user }, secretKey, { expiresIn: '1h' }, (err, token) => {
if (err) {
res.status(500).send('Error generating token');
} else {
res.json({ token });
}
});
});
處理保護頁面
我們使用veriyToken()函數作為中間件來處理每條需要驗證的路由。請求通過,veriyToken()并且僅當next()調用該函數時,它才會傳遞到該路由并實作代碼。
app.get('/dashboard', verifyToken, (req, res) => {
res.send('Welcome to the Home page');
});
// Verify token middleware
function verifyToken(req, res, next) {
const token = req.headers['authorization'];
if (typeof token !== 'undefined') {
jwt.verify(token.split(' ')[1], secretKey, (err, decoded) => {
if (err) {
res.status(403).send('Invalid token');
} else {
req.user = decoded.user;
next();
}
});
} else {
res.status(401).send('Unauthorized');
}
}
兩種方法的差異
- 存儲位置:會話存儲在伺服器上,而令牌(JWT)存儲在用戶端。
- 有狀态與無狀态:會話是有狀态的,而令牌是無狀态的,進而可以在分布式系統中實作更好的可擴充性。
- 過期處理:會話過期由伺服器管理,而令牌過期由令牌本身處理。
- 安全措施:JWT 通常包括數字簽名和加密支援,與使用 cookie 的典型會話機制相比增強了安全性,并且如果保護不當,可能容易受到 CSRF 攻擊。
- 使用靈活性:令牌 (JWT) 在攜帶身份驗證之外的附加資訊方面提供了更大的靈活性,對于授權和自定義資料傳輸非常有用。
應該使用哪種方法?
這取決于應用程式的要求和性質。大多數應用程式會使用混合方法,即針對 API 的基于令牌的身份驗證,以及針對基于 Web 的互動的基于會話的身份驗證。