ExpressJS - 身份验证
Express.js - 现代用户身份验证
Section titled “Express.js - 现代用户身份验证”身份验证 是验证用户身份的过程。在 Web 应用程序中,这通常涉及用户名/电子邮件和密码。本教程演示了一种使用会话构建身份验证系统的现代、安全方法。
安全警告:为简化起见,此示例使用内存数组来存储用户。在实际应用程序中,你必须使用持久化数据库(如 PostgreSQL 或 MongoDB)来存储用户数据。
步骤 1:项目设置与依赖项
Section titled “步骤 1:项目设置与依赖项”首先创建一个新的 Express 项目(请参阅环境设置章节),并安装处理会话和密码哈希所需的包:
# 用于会话管理npm install express-session
# 用于安全地哈希密码npm install bcrypt
# 用于渲染视图的模板引擎npm install pug步骤 2:应用程序结构
Section titled “步骤 2:应用程序结构”良好的结构是关键。创建以下文件和目录:
.├── src/│ ├── views/ # Pug 模板│ │ ├── login.pug│ │ ├── signup.pug│ │ └── protected.pug│ └── index.js # 主应用程序文件├── .env # 环境变量├── package.json└── ...在你的根目录中创建一个 .env 文件来存储你的会话密钥。此文件绝不应提交到 Git。
SESSION_SECRET='a-very-strong-and-long-random-secret-key'步骤 3:主应用程序逻辑 (src/index.js)
Section titled “步骤 3:主应用程序逻辑 (src/index.js)”此文件将包含我们所有的逻辑。在一个更大的应用程序中,你会将其拆分为路由、控制器和服务。
import express from 'express';import session from 'express-session';import bcrypt from 'bcrypt';import 'dotenv/config'; // Loads .env file
const app = express();const PORT = 3000;
// 内存中的用户存储(仅用于演示!)const users = [];
// --- 中间件设置 ---app.set('view engine', 'pug');app.set('views', './src/views');
app.use(express.json());app.use(express.urlencoded({ extended: true }));app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: false } // 在生产环境中,结合 HTTPS 设置为 true}));
// --- 用于保护路由的自定义中间件 ---const checkAuth = (req, res, next) => { if (req.session.user) { next(); // 用户已通过身份验证,继续 } else { res.redirect('/login'); // 未通过身份验证,重定向到登录页 }};
// --- 路由 ---app.get('/', (req, res) => { res.redirect('/login');});
app.get('/signup', (req, res) => { res.render('signup');});
app.post('/signup', async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).render('signup', { message: 'Username and password are required.' }); } if (users.find(user => user.username === username)) { return res.status(400).render('signup', { message: 'Username already exists.' }); }
try { const hashedPassword = await bcrypt.hash(password, 10); // 哈希密码 const newUser = { username, password: hashedPassword }; users.push(newUser); console.log('New user registered:', newUser); res.redirect('/login'); } catch (error) { console.error(error); res.status(500).send('Server error'); }});
app.get('/login', (req, res) => { res.render('login');});
app.post('/login', async (req, res) => { const { username, password } = req.body; const user = users.find(u => u.username === username); if (!user) { return res.status(401).render('login', { message: 'Invalid credentials.' }); }
try { const isMatch = await bcrypt.compare(password, user.password); if (isMatch) { req.session.user = { username: user.username }; // 将用户存储到会话中 res.redirect('/protected'); } else { res.status(401).render('login', { message: 'Invalid credentials.' }); } } catch (error) { console.error(error); res.status(500).send('Server error'); }});
app.get('/protected', checkAuth, (req, res) => { res.render('protected', { username: req.session.user.username });});
app.post('/logout', (req, res) => { req.session.destroy(err => { if (err) { return res.status(500).send('Could not log out.'); } res.redirect('/login'); });});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));步骤 4:创建视图 (Pug 模板)
Section titled “步骤 4:创建视图 (Pug 模板)”在你的 src/views 目录中创建以下三个 .pug 文件。
signup.pug
Section titled “signup.pug”html head title Signup body h1 Create an Account if message p(style={color: 'red'}) #{message} form(action="/signup" method="POST") div label(for="username") Username: input(name="username" type="text" required) div label(for="password") Password: input(name="password" type="password" required) button(type="submit") Sign Up p Already have an account? <a href="/login">Login</a>login.pug
Section titled “login.pug”html head title Login body h1 Login if message p(style={color: 'red'}) #{message} form(action="/login" method="POST") div label(for="username") Username: input(name="username" type="text" required) div label(for="password") Password: input(name="password" type="password" required) button(type="submit") Log In p Don't have an account? <a href="/signup">Sign up</a>protected.pug
Section titled “protected.pug”html head title Protected Area body h1 Welcome, #{username}! p This is a protected page. You can only see it if you are logged in. form(action="/logout" method="POST") button(type="submit") Log Out运行和测试应用程序
Section titled “运行和测试应用程序”运行 npm run dev 并导航到 http://localhost:3000。你将被重定向到登录页面。尝试直接访问 /protected;你将被送回登录页。现在,注册一个新账户,然后使用这些凭据登录以访问受保护页面。
现代替代方案:JWT (JSON Web Tokens)
Section titled “现代替代方案:JWT (JSON Web Tokens)”虽然会话很棒,但另一种流行的身份验证方法是使用 JWT。这是一种无状态方法,意味着服务器无需存储会话数据。流程如下:
- 用户登录。
- 服务器验证凭据并向客户端返回一个签名的 JWT。
- 客户端存储 JWT(例如,在
HttpOnlycookie 中),并在后续请求的Authorization头部中发送它。 - 服务器在每个请求上验证 JWT 以识别用户。 此方法对于单页应用程序 (SPA) 和移动应用程序特别有用。