在現代Web應用中,用戶鑒權是一個非常重要的環節。傳統的Session機制雖然簡單易用,但在分布式系統中存在一些問題,如Session共享、跨域問題等。為了解決這些問題,JWT(JSON Web Token)應運而生。JWT是一種輕量級的、自包含的、基于JSON的開放標準(RFC 7519),用于在各方之間安全地傳輸信息。本文將詳細介紹如何在Node.js中實現JWT鑒權機制。
JWT(JSON Web Token)是一種開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用于在各方之間作為JSON對象安全地傳輸信息。JWT通常用于身份驗證和信息交換,特別是在分布式系統中。
JWT由三部分組成,分別是Header、Payload和Signature,它們之間用.
分隔。具體結構如下:
Header.Payload.Signature
Header通常由兩部分組成:令牌的類型(即JWT)和所使用的簽名算法(如HMAC SHA256或RSA)。例如:
{
"alg": "HS256",
"typ": "JWT"
}
Payload包含聲明(Claims),聲明是關于實體(通常是用戶)和其他數據的聲明。聲明分為三種類型:
iss
(簽發者)、exp
(過期時間)、sub
(主題)等。例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature用于驗證消息在傳輸過程中沒有被篡改。它通過對Header和Payload進行Base64Url編碼后,使用Header中指定的算法進行簽名生成。例如,使用HMAC SHA256算法的簽名如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
在Node.js中實現JWT鑒權機制,首先需要安裝jsonwebtoken
庫。該庫提供了生成和驗證JWT的功能。
npm install jsonwebtoken
生成JWT的過程非常簡單,只需要調用jsonwebtoken
庫的sign
方法即可。以下是一個生成JWT的示例:
const jwt = require('jsonwebtoken');
const payload = {
userId: 123,
username: 'john_doe'
};
const secret = 'your-secret-key';
const options = {
expiresIn: '1h' // 設置Token過期時間為1小時
};
const token = jwt.sign(payload, secret, options);
console.log('Generated Token:', token);
驗證JWT的過程同樣簡單,只需要調用jsonwebtoken
庫的verify
方法即可。以下是一個驗證JWT的示例:
const jwt = require('jsonwebtoken');
const token = 'your-generated-token';
const secret = 'your-secret-key';
jwt.verify(token, secret, (err, decoded) => {
if (err) {
console.error('Token verification failed:', err.message);
} else {
console.log('Decoded Token:', decoded);
}
});
JWT通常存儲在客戶端的LocalStorage或Cookie中,并在每次請求時通過Authorization頭發送給服務器。以下是一個在Express應用中處理JWT的示例:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';
app.use(express.json());
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 模擬用戶驗證
if (username === 'john_doe' && password === 'password') {
const payload = { userId: 123, username: 'john_doe' };
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});
app.get('/protected', (req, res) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
res.json({ message: 'Access granted', user: decoded });
});
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
在實現JWT鑒權機制之前,首先需要實現用戶注冊與登錄功能。以下是一個簡單的用戶注冊與登錄的示例:
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';
app.use(express.json());
const users = [];
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 檢查用戶名是否已存在
if (users.find(user => user.username === username)) {
return res.status(400).json({ message: 'Username already exists' });
}
// 哈希密碼
const hashedPassword = await bcrypt.hash(password, 10);
// 保存用戶信息
const user = { id: users.length + 1, username, password: hashedPassword };
users.push(user);
res.status(201).json({ message: 'User registered successfully' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 查找用戶
const user = users.find(user => user.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 驗證密碼
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 生成JWT
const payload = { userId: user.id, username: user.username };
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
在用戶登錄成功后,服務器會生成JWT并返回給客戶端??蛻舳嗽诿看握埱髸r攜帶JWT,服務器在接收到請求后驗證JWT的有效性。以下是一個完整的JWT生成與驗證的示例:
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';
app.use(express.json());
const users = [];
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 檢查用戶名是否已存在
if (users.find(user => user.username === username)) {
return res.status(400).json({ message: 'Username already exists' });
}
// 哈希密碼
const hashedPassword = await bcrypt.hash(password, 10);
// 保存用戶信息
const user = { id: users.length + 1, username, password: hashedPassword };
users.push(user);
res.status(201).json({ message: 'User registered successfully' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 查找用戶
const user = users.find(user => user.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 驗證密碼
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 生成JWT
const payload = { userId: user.id, username: user.username };
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token });
});
app.get('/protected', (req, res) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
res.json({ message: 'Access granted', user: decoded });
});
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
在Express應用中,可以通過中間件來保護需要鑒權的路由。以下是一個保護路由的示例:
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';
app.use(express.json());
const users = [];
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
req.user = decoded;
next();
});
};
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 檢查用戶名是否已存在
if (users.find(user => user.username === username)) {
return res.status(400).json({ message: 'Username already exists' });
}
// 哈希密碼
const hashedPassword = await bcrypt.hash(password, 10);
// 保存用戶信息
const user = { id: users.length + 1, username, password: hashedPassword };
users.push(user);
res.status(201).json({ message: 'User registered successfully' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 查找用戶
const user = users.find(user => user.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 驗證密碼
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 生成JWT
const payload = { userId: user.id, username: user.username };
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token });
});
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'Access granted', user: req.user });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
JWT通常有一個較短的有效期,為了延長用戶的會話時間,可以通過刷新Token的機制來實現。以下是一個刷新Token的示例:
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';
app.use(express.json());
const users = [];
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
req.user = decoded;
next();
});
};
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 檢查用戶名是否已存在
if (users.find(user => user.username === username)) {
return res.status(400).json({ message: 'Username already exists' });
}
// 哈希密碼
const hashedPassword = await bcrypt.hash(password, 10);
// 保存用戶信息
const user = { id: users.length + 1, username, password: hashedPassword };
users.push(user);
res.status(201).json({ message: 'User registered successfully' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 查找用戶
const user = users.find(user => user.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 驗證密碼
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// 生成JWT
const payload = { userId: user.id, username: user.username };
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token });
});
app.post('/refresh-token', (req, res) => {
const { token } = req.body;
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
// 生成新的JWT
const payload = { userId: decoded.userId, username: decoded.username };
const newToken = jwt.sign(payload, secret, { expiresIn: '1h' });
res.json({ token: newToken });
});
});
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'Access granted', user: req.user });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
盡管JWT在分布式系統中非常有用,但它也存在一些安全隱患:
為了增強JWT的安全性,可以采取以下措施:
JWT是一種輕量級、自包含的鑒權機制,適合在分布式系統中使用。通過本文的介紹,我們了解了如何在Node.js中實現JWT鑒權機制,包括用戶注冊與登錄、JWT的生成與驗證、保護路由、刷新Token等。同時,我們也探討了JWT的安全隱患以及如何增強其安全性。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。