在本文中,我们将了解后端应用程序中使用的基于会话和令牌的身份验证方法。
基于会话的身份验证
简单讲,基于会话(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),其允许对网站或应用程序进行安全且经过验证的访问,而无需重复登录。
让我们看一下基于令牌的身份验证的分步工作流程。
下面是一个使用 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),其允许对网站或应用程序进行安全且经过验证的访问,而无需重复登录。
让我们看一下基于令牌的身份验证的分步工作流程。
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
}
});
});
用户登录请求:
用户通过特定请求将电子邮件和密码发送到服务器来登录。
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 的交互的基于会话的身份验证。
希望你喜欢这篇文章。如果对你有用,请不要忘记点赞转发!对了,你的项目使用哪种后端语言?欢迎在文底发表评论~🤔
//登录
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) 在携带身份验证之外的附加信息方面提供了更大的灵活性,对于授权和自定义数据传输非常有用。