# 登录与用户中心

URL: https://caijiao.org/express/chapter11/4-user-center
Source: docs/express/chapter11/4-user-center.md
Description: 本章节将介绍如何在 Express 中实现登录功能及用户中心管理系统。我们将使用 **Passport.js** 实现用户认证，结合 **JWT** 实现会话管理，并创建一个简单的用户中心，供用户查看和管理个人信息。

在本章节中，我们将通过构建一个简单的登录与用户中心系统，介绍如何在 Express 中实现用户身份认证、登录状态管理以及用户信息的查看与修改。我们将使用 **Passport.js** 来处理用户认证，**JWT** 用于会话管理，并且实现基本的用户信息存储与管理。

## 1. 安装依赖

首先，您需要安装用于处理身份认证和用户登录的必要依赖：

```bash
npm install passport passport-local jsonwebtoken bcryptjs
````

* **passport**：用于处理身份验证的中间件。
* **passport-local**：实现本地用户名和密码认证策略。
* **jsonwebtoken (JWT)**：用于创建和验证 JSON Web Tokens，用于会话管理。
* **bcryptjs**：用于加密和验证用户密码。

## 2. 设置 Passport.js 认证

### 2.1 用户模型设计

我们将使用 Mongoose 来创建一个用户模型，包含用户名、密码、邮箱和注册时间等基本信息。

```js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

// 创建用户模型
const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  createdAt: { type: Date, default: Date.now },
});

// 加密密码
userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();

  this.password = await bcrypt.hash(this.password, 10);
  next();
});

// 比较密码
userSchema.methods.comparePassword = function (candidatePassword) {
  return bcrypt.compare(candidatePassword, this.password);
};

const User = mongoose.model('User', userSchema);

module.exports = User;
```

### 2.2 配置 Passport.js

我们将使用 Passport 的 **local strategy** 来处理用户名和密码的身份验证。

```js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User');

// 配置 Passport 本地策略
passport.use(new LocalStrategy(
  async (username, password, done) => {
    try {
      const user = await User.findOne({ username });
      if (!user) return done(null, false, { message: 'Incorrect username' });

      const isMatch = await user.comparePassword(password);
      if (!isMatch) return done(null, false, { message: 'Incorrect password' });

      return done(null, user);
    } catch (err) {
      return done(err);
    }
  }
));

// 序列化用户
passport.serializeUser((user, done) => {
  done(null, user.id);
});

// 反序列化用户
passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});
```

## 3. 用户注册与登录路由

### 3.1 用户注册

用户需要提供用户名、邮箱和密码来注册。我们将使用 POST 请求处理注册，并在数据库中保存用户信息。

```js
const express = require('express');
const User = require('../models/User');
const passport = require('passport');

const router = express.Router();

// 注册路由
router.post('/register', async (req, res) => {
  const { username, email, password } = req.body;

  try {
    const existingUser = await User.findOne({ username });
    if (existingUser) return res.status(400).json({ message: 'Username already taken' });

    const newUser = new User({ username, email, password });
    await newUser.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (err) {
    res.status(500).json({ message: 'Error registering user', error: err });
  }
});
```

### 3.2 用户登录

用户可以使用用户名和密码进行登录，成功后返回一个 **JWT**，用于后续的请求认证。

```js
const jwt = require('jsonwebtoken');

// 登录路由
router.post('/login', passport.authenticate('local', { session: false }), (req, res) => {
  const user = req.user;

  // 生成 JWT
  const token = jwt.sign({ id: user.id, username: user.username }, 'secretKey', { expiresIn: '1h' });

  res.status(200).json({
    message: 'Login successful',
    token: token,
    user: {
      id: user.id,
      username: user.username,
      email: user.email,
    }
  });
});
```

### 3.3 用户中心

登录后，用户可以通过访问用户中心查看和修改个人信息。我们通过验证 JWT 来保护该路由。

```js
const jwtMiddleware = require('../middleware/jwtMiddleware');  // 自定义的 JWT 校验中间件

// 用户中心路由，保护路由，仅允许已认证用户访问
router.get('/user-center', jwtMiddleware, async (req, res) => {
  try {
    const user = await User.findById(req.user.id);
    if (!user) return res.status(404).json({ message: 'User not found' });

    res.status(200).json({
      user: {
        id: user.id,
        username: user.username,
        email: user.email,
        createdAt: user.createdAt,
      }
    });
  } catch (err) {
    res.status(500).json({ message: 'Error retrieving user data', error: err });
  }
});
```

## 4. JWT 验证中间件

我们需要创建一个中间件来验证 JWT，以便保护需要用户登录的路由。

```js
const jwt = require('jsonwebtoken');

const jwtMiddleware = (req, res, next) => {
  const token = req.headers['authorization'];

  if (!token) return res.status(403).json({ message: 'No token provided' });

  jwt.verify(token, 'secretKey', (err, decoded) => {
    if (err) return res.status(401).json({ message: 'Invalid or expired token' });

    req.user = decoded;  // 将解码后的用户信息添加到请求中
    next();
  });
};

module.exports = jwtMiddleware;
```

## 5. 用户信息更新

用户可以更新自己的个人信息，例如修改邮箱或密码。我们将提供一个 PUT 路由来处理更新请求。

```js
router.put('/user-center', jwtMiddleware, async (req, res) => {
  const { email, password } = req.body;
  const { id } = req.user;

  try {
    const user = await User.findById(id);
    if (!user) return res.status(404).json({ message: 'User not found' });

    if (email) user.email = email;
    if (password) user.password = await bcrypt.hash(password, 10);  // 加密新密码

    await user.save();
    res.status(200).json({ message: 'User information updated' });
  } catch (err) {
    res.status(500).json({ message: 'Error updating user data', error: err });
  }
});
```

## 6. 总结

通过本章节的实现，您已经掌握了如何在 Express 中构建一个基本的登录和用户中心系统。您学会了如何：

1. 使用 Passport.js 进行身份验证，处理用户的登录请求。
2. 使用 JWT 来管理用户的会话，并实现对用户中心等敏感路由的保护。
3. 创建用户注册和登录功能，处理用户信息的更新。

您可以根据需要扩展更多的功能，如密码重置、用户角色管理等，来进一步完善该系统。
