Koa2+JWT 完整实战项目

本文介绍如何使用 Koa2 和 JWT 实现一个完整的前端项目。主要涵盖以下内容:

  • Koa2 的安装和使用
  • JWT 的介绍和使用
  • Koa2 和 JWT 结合的实现方式
  • 完整实战项目

Koa2 的安装和使用

Koa2 是一个 Node.js 的 web 框架,用于编写 Web 应用程序和 API。它构建在 ES6 的语法之上,具有小巧、灵活、易于扩展的特点。

首先,需要安装和配置 Node.js,在命令行中执行以下命令:

安装完成后,新建一个文件夹作为项目的根目录,使用以下代码引入 Koa2:

const Koa = require('koa');
const app = new Koa();

app.listen(3000);
console.log('app started at http://localhost:3000');

在命令行中执行 node app.js,即可启动服务并访问 http://localhost:3000。

JWT 的介绍和使用

JWT(JSON Web Token)是一种身份验证(Authentication)方式,用于在客户端和服务端之间传递认证信息。它由三部分组成:Header、Payload 和 Signature。

其中,Header 用于描述 JWT 的基本信息,包括算法、类型等;Payload 是实际携带的用户信息,以及 JWT 的有效期等;Signature 则是用于验证 JWT 是否合法的密钥。

JWT 有以下几个优点:

  • 安全性高:JWT 的 Payload 可以进行加密,防止信息泄露;同时,服务端也需要验证 Signature 是否正确,防止篡改。
  • 可扩展性强:JWT 中的 Payload 可以包含非常多的信息,例如用户权限、角色等等,可以根据实际需求进行灵活扩展。
  • 无状态:JWT 不需要在服务端保存任何东西,也不需要在客户端保存 sessionID 等信息,可以降低服务端的压力和复杂度。

JWT 的使用过程如下:

  1. 客户端向服务端发送用户名和密码。
  2. 服务端验证用户名和密码,并生成一个 JWT。
  3. 服务端将 JWT 发送给客户端。
  4. 客户端在以后的请求中携带 JWT。
  5. 服务端验证 JWT 是否合法,并对用户进行身份认证。

下面给出一个 JWT 的示例代码:

const jwt = require('jsonwebtoken');

const payload = { user: 'username' };
const secretKey = 'secret';

const token = jwt.sign(payload, secretKey, { expiresIn: 3600 });
console.log(token);

const decoded = jwt.verify(token, secretKey);
console.log(decoded);

Koa2 和 JWT 结合的实现方式

在 Koa2 中结合 JWT 的实现方式如下:

  1. 客户端在登录成功后,将生成的 JWT 存储在 LocalStorage 或 Cookie 中。
  2. 在每次请求时,将 JWT 携带在请求的 HTTP Header 中,例如 Authorization: Bearer <token>
  3. 服务端在收到请求后,从 Header 中获取 JWT 并进行验证。验证通过的话,就认为用户已登录,并执行后续的操作。

下面给出一个 Koa2 和 JWT 的实现示例:

const Koa = require('koa');
const jwt = require('jsonwebtoken');

const app = new Koa();
const secretKey = 'secret'; // 这里应该将密钥保存在环境变量中

app.use(async (ctx, next) => {
  if (ctx.path === '/login') {
    const { username, password } = ctx.request.body;
    // 验证用户名和密码,此处省略

    const payload = { user: username };
    const token = jwt.sign(payload, secretKey, { expiresIn: 3600 });
    ctx.body = { token };
  } else {
    const header = ctx.headers.authorization;
    if (!header) {
      ctx.status = 401;
      return;
    }

    const tokenArr = header.split(' ');
    if (tokenArr[0] !== 'Bearer' || !tokenArr[1]) {
      ctx.status = 401;
      return;
    }

    try {
      const decoded = jwt.verify(tokenArr[1], secretKey);
      ctx.state.user = decoded.user; // 将用户信息保存到上下文中
      await next();
    } catch (err) {
      ctx.status = 401;
    }
  }
});

app.use(async (ctx) => {
  const user = ctx.state.user; // 获取用户信息
  ctx.body = `Hello, ${user}!`;
});

app.listen(3000);
console.log('app started at http://localhost:3000');

完整实战项目

前面我们已经讲解了 Koa2 和 JWT 的基本使用方式,下面我们将结合实战项目来演示如何将它们应用到一个完整的前端项目中。

项目需求

我们的项目需求是:实现一个用户登录系统,用户可以进行登录、注册、登出和访问授权资源等操作。

项目技术栈

前端使用 React + Ant Design,后端使用 Koa2,数据库使用 MongoDB。

项目结构

前端代码:

后端代码:

实现步骤

前端实现

  1. 安装 React 和 Ant Design。
  1. 实现登录页面。

将 Login 组件放在 components/Login.js 中。

import React, { useState } from 'react';
import { Form, Input, Button } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { login } from '../utils/api';

function LoginForm({ onSuccess }) {
  const [isLoading, setIsLoading] = useState(false);

  const handleSubmit = async (values) => {
    setIsLoading(true);
    try {
      await login(values.username, values.password);
      onSuccess();
    } catch (err) {
      alert('登录失败!');
      setIsLoading(false);
    }
  };

  return (
    <Form
      name="login"
      initialValues={{ remember: true }}
      onFinish={handleSubmit}
      autoComplete="off"
    >
      <Form.Item
        name="username"
        rules={[{ required: true, message: '请输入用户名!' }]}
      >
        <Input
          prefix={<UserOutlined />}
          placeholder="用户名"
          autoComplete="off"
          autoFocus={true}
        />
      </Form.Item>
      <Form.Item
        name="password"
        rules={[{ required: true, message: '请输入密码!' }]}
      >
        <Input
          prefix={<LockOutlined />}
          type="password"
          placeholder="密码"
          autoComplete="off"
        />
      </Form.Item>
      <Form.Item>
        <Button
          type="primary"
          htmlType="submit"
          loading={isLoading}
          style={{ width: '100%' }}
        >
          登录
        </Button>
      </Form.Item>
    </Form>
  );
}

export default LoginForm;
  1. 实现 App 组件。

将 Header 和 Footer 组件放在 components/Header.jscomponents/Footer.js 中。

import React, { useState } from 'react';
import { Layout, Menu } from 'antd';
import LoginForm from './Login';

const { Header, Content, Footer } = Layout;

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLogin = () => {
    setIsLoggedIn(true);
  };

  const handleLogout = () => {
    setIsLoggedIn(false);
  };

  return (
    <Layout>
      <Header>
        <Menu theme="dark" mode="horizontal" defaultSelectedKeys={['home']}>
          <Menu.Item key="home">首页</Menu.Item>
          {isLoggedIn ? (
            <Menu.Item key="logout" onClick={handleLogout}>
              退出
            </Menu.Item>
          ) : (
            <Menu.Item key="login">
              <LoginForm onSuccess={handleLogin} />
            </Menu.Item>
          )}
        </Menu>
      </Header>
      <Content style={{ padding: '24px 50px' }}>
        <div style={{ background: '#fff', minHeight: 280 }}>Content</div>
      </Content>
      <Footer style={{ textAlign: 'center' }}>
        ©2021 Created by Me
      </Footer>
    </Layout>
  );
}

export default App;
  1. 实现 API 封装。

utils/api.js 中实现以下代码:

import axios from 'axios';

axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  config.headers.Authorization = `Bearer ${token}`;
  return config;
});

export async function login(username, password) {
  const { data } = await axios.post('/api/login', { username, password });
  localStorage.setItem('token', data.token);
}

export async function logout() {
  localStorage.removeItem('token');
}
  1. 实现鉴权。

utils/auth.js 中实现以下代码:

export function isAuthenticated() {
  const token = localStorage.getItem('token');
  return !!token;
}

后端实现

  1. 安装 Koa2 和相关依赖。
  1. 初始化数据库。

src/db/index.js 中实现以下代码:

const mongoose = require('mongoose');
const { DB_HOST, DB_PORT, DB_DATABASE } = require('../utils/config');

mongoose.connect(`mongodb://${DB_HOST}:${DB_PORT}/${DB_DATABASE}`, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});
mongoose.connection.once('open', () => {
  console.log('Database connected!');
});
mongoose.connection.on('error', (err) => {
  console.error(err);
});
  1. 实现路由。

src/routes/index.js 中实现以下代码:

const KoaRouter = require('koa-router');
const router = new KoaRouter();

const auth = require('./auth');

router.use('/api/auth', auth.routes());

module.exports = router;

src/routes/auth.js 中实现以下代码:

const KoaRouter = require('koa-router');
const jwt = require('../utils/jwt');
const User = require('../db/models/user');

const router = new KoaRouter();

router.post('/login', async (ctx) => {
  const { username, password } = ctx.request.body;

  const user = await User.findOne({ username }).select('+password');
  if (!user || !(await user.matchPassword(password))) {
    ctx.throw(400, '用户名或密码错误');
  }

  const token = jwt.sign({ user: user.id });

  ctx.body = { token };
});

router.post('/logout', async (ctx) => {
  ctx.body = { message: 'logout' };
});

router.get('/me', async (ctx) => {
  const { user } = ctx.state;
  ctx.body = { user };
});

module.exports = router;
  1. 实现身份验证。

src/middleware/auth.js 中实现以下代码:

const jwt = require('../utils/jwt');
const User = require('../db/models/user');

module.exports = async (ctx, next) => {
  const header = ctx.headers.authorization;
  if (!header) {
    ctx.throw(401);
  }

  const tokenArr = header.split(' ');
  if (tokenArr[0] !== 'Bearer' || !tokenArr[1]) {
    ctx.throw(401);
  }

  try {
    const { user } = jwt.verify(tokenArr[1]);
    ctx.state.user = await User.findById(user);
    await next();
  } catch (err) {
    ctx.throw(401);
  }
};
  1. 实现启动脚本。

src/index.js 中实现以下代码:

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const logger = require('koa-logger');
const router = require('./routes');
const db = require('./db');
const authMiddleware = require('./middleware/auth');

const app = new Koa();

app.use(logger());
app.use(bodyParser());
app.use(authMiddleware);
app.use(router.routes());

app.listen(4000, () => {
  console.log('Server started on http://localhost:4000');
  db.connect();
});

项目演示

在前端和后端目录中分别执行以下命令:

然后,在浏览器中访问 http://localhost:3000 即可看到登录页面。输入用户名和密码后,点击登录按钮,就可以进入到首页了。

在登录状态下,再次访问登录页面,将会看到已经成功登录的状态,可以进行登出操作。

在后端中,各个路由都需要身份验证,验证通过后才可以进行访问。

总结

本文介绍了如何使用 Koa2 和 JWT 来实现前端项目中的用户登录、访问授权等功能,详细讲解了 Koa2 和 JWT 的基本使用方式,并通过一个实际的项目来演示了如何将它们结合起来实现一个完整的前端项目。希望本文对大家能有所帮助。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6593ed01eb4cecbf2d88a7db


纠错反馈