# 实现登录系统

URL: https://caijiao.org/express/chapter8/2-login
Source: docs/express/chapter8/2-login.md
Description: 本章节将介绍如何使用 Express 和相关中间件实现一个简单的用户登录系统。我们将使用 Cookie 或 Session 来存储用户登录状态，并处理用户认证流程。

在 Web 应用中，用户登录系统是大多数应用的基本需求。一个完整的登录系统包括用户身份验证、会话管理、登录状态保持等功能。使用 Express 可以快速构建一个简单的登录系统。在本章节中，我们将展示如何在 Express 中实现一个基本的登录系统，并利用 Session 或 Cookie 来保持用户的登录状态。

## 步骤概览

1. 创建用户注册和登录接口。
2. 使用 Session 管理用户登录状态。
3. 保护需要认证的路由，确保用户登录后才能访问。
4. 提供退出登录的功能。

## 1. 设置 Express 项目

首先，我们需要搭建一个基本的 Express 项目，并安装需要的依赖：

```bash
npm init -y
npm install express express-session body-parser
````

接着，创建一个基本的 Express 应用：

```js
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

// 使用 body-parser 解析 POST 请求的表单数据
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 使用 session 来管理用户登录状态
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false }  // 开发环境设置为 false
}));

// 用户数据库模拟
const users = [
  { username: 'john_doe', password: 'password123' },
  { username: 'jane_doe', password: '12345' }
];

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});
```

## 2. 用户注册与登录

### 2.1 用户注册

在实际项目中，用户的注册需要将用户数据存储到数据库中。在本示例中，我们通过一个简单的内存数组来模拟数据库。用户注册时，我们会检查用户名是否已经存在，如果不存在则将用户添加到“数据库”中。

```js
app.post('/register', (req, res) => {
  const { username, password } = req.body;

  // 检查用户名是否已经存在
  const userExists = users.some(user => user.username === username);
  if (userExists) {
    return res.status(400).send('用户名已存在');
  }

  // 将新用户添加到 "数据库" 中
  users.push({ username, password });
  res.send('注册成功');
});
```

### 2.2 用户登录

用户登录时，我们需要验证其用户名和密码。如果验证成功，我们会将用户的登录状态存储在 Session 中，以便后续请求可以识别该用户。

```js
app.post('/login', (req, res) => {
  const { username, password } = req.body;

  // 查找匹配的用户
  const user = users.find(u => u.username === username && u.password === password);
  if (!user) {
    return res.status(401).send('用户名或密码错误');
  }

  // 登录成功，将用户信息存储在 session 中
  req.session.user = user;
  res.send('登录成功');
});
```

## 3. 保护需要认证的路由

为了确保用户在登录后才能访问某些敏感页面或操作，我们需要在相关路由上加上认证中间件。这个中间件会检查 Session 中是否存有用户数据，如果没有，则返回未授权的错误。

```js
// 认证中间件
function isAuthenticated(req, res, next) {
  if (req.session.user) {
    return next();  // 用户已登录，继续执行下一个中间件
  }
  res.status(403).send('未授权访问');
}

// 保护需要认证的路由
app.get('/dashboard', isAuthenticated, (req, res) => {
  res.send(`欢迎回来，${req.session.user.username}`);
});
```

在上面的代码中，`isAuthenticated` 中间件会检查用户是否已经登录（即 `req.session.user` 是否存在）。如果用户没有登录，它会返回一个 403 错误。

## 4. 退出登录

退出登录的功能通常是通过销毁当前用户的 Session 实现的。当用户点击退出时，Session 将被销毁，用户将无法访问受保护的页面。

```js
app.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).send('退出登录失败');
    }
    res.send('退出登录成功');
  });
});
```

在这段代码中，`req.session.destroy()` 会销毁当前用户的会话，用户将被退出。

## 5. 完整代码示例

以下是完整的 Express 应用代码，实现了用户注册、登录、退出以及保护的功能：

```js
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

// 使用 body-parser 解析 POST 请求的表单数据
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 使用 session 来管理用户登录状态
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false }  // 开发环境设置为 false
}));

// 用户数据库模拟
const users = [
  { username: 'john_doe', password: 'password123' },
  { username: 'jane_doe', password: '12345' }
];

// 认证中间件
function isAuthenticated(req, res, next) {
  if (req.session.user) {
    return next();  // 用户已登录，继续执行下一个中间件
  }
  res.status(403).send('未授权访问');
}

// 注册路由
app.post('/register', (req, res) => {
  const { username, password } = req.body;

  const userExists = users.some(user => user.username === username);
  if (userExists) {
    return res.status(400).send('用户名已存在');
  }

  users.push({ username, password });
  res.send('注册成功');
});

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body;

  const user = users.find(u => u.username === username && u.password === password);
  if (!user) {
    return res.status(401).send('用户名或密码错误');
  }

  req.session.user = user;
  res.send('登录成功');
});

// 保护的路由
app.get('/dashboard', isAuthenticated, (req, res) => {
  res.send(`欢迎回来，${req.session.user.username}`);
});

// 退出登录
app.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).send('退出登录失败');
    }
    res.send('退出登录成功');
  });
});

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});
```

## 总结

本章节介绍了如何在 Express 中实现一个简单的登录系统，包含以下主要步骤：

1. **用户注册与登录**：通过 POST 请求接收用户提交的表单数据并验证，成功登录后将用户信息存储在 Session 中。
2. **保护路由**：使用中间件保护需要用户认证才能访问的路由，确保只有登录用户才能访问这些路由。
3. **退出登录**：销毁用户的 Session，使其退出登录状态。

通过这种方式，我们可以轻松实现一个基于 Session 的用户登录系统，并且可以根据实际需求扩展更多功能。
