JWT是什么?怎么用?

129 阅读7分钟

JWT应用场景

JWT一般用于身份验证和权限控制。

JWT(JSON Web Token)是一种用于在各方之间传递安全信息的紧凑且自包含的方式。以下是一些常见的使用场景及其在服务端路径访问控制中的应用:

常见使用场景

  1. 身份验证(Authentication)

    • 用户登录时,服务器生成JWT并返回给客户端。客户端存储JWT(通常在本地存储或Cookies中),并在后续请求中将其作为身份验证凭据发送给服务器。
    • 服务器在每个受保护的端点中验证JWT,确保请求来自已认证用户。
  2. 信息交换(Information Exchange)

    • 双方可以使用JWT安全地交换信息,因为它们可以被签名以验证内容的真实性和完整性。

服务端路径访问控制

在服务端路径访问控制中,JWT通常用于保护特定资源和路径。以下是一个基本的流程示例:

  1. 用户登录

    • 用户提供凭证(例如用户名和密码)并发送到服务器。
    • 服务器验证凭证,并在成功后生成一个JWT,其中包含用户的身份信息和权限。
  2. 客户端存储JWT

    • 服务器将生成的JWT返回给客户端。
    • 客户端将JWT存储在本地存储或Cookies中,以便在后续请求中使用。
  3. 请求受保护资源

    • 客户端在每个请求中将JWT包含在请求头中(通常是Authorization: Bearer <token>)。
    • 服务器在每次请求时验证JWT,确保其有效性和完整性。
  4. 服务器验证JWT

    • 服务器使用预先定义的密钥或公钥来验证JWT的签名。
    • 服务器检查JWT的有效期,确保它未过期。
    • 服务器解析JWT,获取用户身份信息和权限。
  5. 访问控制

    • 根据JWT中包含的用户身份信息和权限,服务器决定是否允许访问请求的资源或路径。
    • 如果验证通过且用户具有访问权限,服务器返回请求的资源。
    • 如果验证失败或用户无权访问,服务器返回适当的错误响应(如401 Unauthorized或403 Forbidden)。

什么是JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。JWT广泛应用于身份验证和信息交换。它由三部分组成:Header(头部)、Payload(负载)和Signature(签名),并且以Header.Payload.Signature的形式存在。

JWT 的结构

  1. Header(头部) :通常包含两部分:令牌的类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。

    json
    复制代码
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  2. Payload(负载) :包含声明(claims),即想要传递的信息。声明通常包括用户身份信息、token的过期时间等。这个部分是可读的,但未加密。

    json
    复制代码
    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
    
  3. Signature(签名) :为了保证消息在传递过程中不被篡改,JWT使用头部和负载,以及一个密钥(在HS256算法中是一个共享密钥,或者在RSA中是一个私钥)进行签名。

    plaintext
    复制代码
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret
    )
    

使用JWT

  1. 创建JWT

    • 安装JWT库,例如在Node.js中可以使用jsonwebtoken库。
    • 编写代码生成JWT。
    javascript
    复制代码
    const jwt = require('jsonwebtoken');
    const token = jwt.sign({ id: user.id }, 'your-256-bit-secret', { expiresIn: '1h' });
    
  2. 验证JWT

    • 在接收到请求时,验证JWT的合法性。
    javascript
    复制代码
    const jwt = require('jsonwebtoken');
    const token = req.header('Authorization').replace('Bearer ', '');
    try {
      const decoded = jwt.verify(token, 'your-256-bit-secret');
      req.user = decoded;
    } catch (error) {
      res.status(401).send({ error: 'Invalid token' });
    }
    
  3. 常见使用场景

    • 身份验证:在用户登录后,服务器生成JWT并返回给客户端,客户端在后续请求中将该JWT作为Authorization头部的一部分发送给服务器。
    • 信息交换:可以在不同服务之间安全地传递信息,因为JWT是自包含的,可以携带必要的信息。

实践例子

以下是一个使用Express和jsonwebtoken库创建和验证JWT的简单示例:

  1. 安装依赖

    bash
    复制代码
    npm install express jsonwebtoken
    
  2. 创建服务器并生成JWT

    javascript
    复制代码
    const express = require('express');
    const jwt = require('jsonwebtoken');
    const app = express();
    
    app.use(express.json());
    
    const secretKey = 'your-256-bit-secret';
    
    // 登录接口,生成JWT
    app.post('/login', (req, res) => {
      const { username, password } = req.body;
      // 假设登录验证通过
      const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
      res.json({ token });
    });
    
    // 验证JWT的中间件
    const authenticate = (req, res, next) => {
      const token = req.header('Authorization').replace('Bearer ', '');
      try {
        const decoded = jwt.verify(token, secretKey);
        req.user = decoded;
        next();
      } catch (error) {
        res.status(401).send({ error: 'Invalid token' });
      }
    };
    
    // 受保护的路由
    app.get('/protected', authenticate, (req, res) => {
      res.send(`Welcome, ${req.user.username}`);
    });
    
    app.listen(3000, () => {
      console.log('Server running on port 3000');
    });
    

这样,通过JWT可以在不需要存储会话状态的情况下,安全地在客户端和服务器之间传递身份验证信息。

JWT 认证涉及两个关键的密码

  1. 服务端的SECRET_KEY:这是服务器端固定的密钥,用于验证生成的令牌(token)是否由服务器创建。每次服务器生成或验证JWT时都会使用这个密钥。
  2. 客户端的用户名和密码:客户端提供的用户名和密码用于与数据库中的相应信息进行比对。如果比对成功,服务器会使用SECRET_KEY对客户信息进行签名,生成JWT,并发送给客户端。客户端在后续请求中携带这个JWT(token),服务器通过验证这个令牌(token),确认客户端已登录并经过验证。

注意,这个客户端的密码存储在数据库中也需要加密

为了确保客户端的密码在数据库中安全存储,常见的做法是对密码进行哈希处理,并使用适当的哈希算法和盐值来增加安全性。以下是一个使用Node.js和bcrypt库对密码进行哈希处理的例子:

安装依赖

bash
复制代码
npm install bcrypt

使用bcrypt加密和验证密码

以下代码示例演示了如何使用bcrypt加密密码并在用户登录时进行密码验证:

  1. 加密密码并存储到数据库
javascript
复制代码
const bcrypt = require('bcrypt');
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your-256-bit-secret';
const users = []; // 用于存储用户的示例数据库

app.use(express.json());

const saltRounds = 10;

app.post('/register', async (req, res) => {
  const { username, password } = req.body;

  // 对密码进行哈希处理
  const hashedPassword = await bcrypt.hash(password, saltRounds);

  // 将用户名和哈希处理后的密码存储到数据库
  users.push({ username, password: hashedPassword });
  res.send('User registered successfully');
});

app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  // 查找用户
  const user = users.find(u => u.username === username);
  if (!user) {
    return res.status(400).send('Invalid username or password');
  }

  // 验证密码
  const match = await bcrypt.compare(password, user.password);
  if (!match) {
    return res.status(400).send('Invalid username or password');
  }

  // 生成JWT
  const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
  res.json({ token });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

解释

  1. 安装bcrypt库:我们首先安装了bcrypt库,它是一个用于密码哈希处理的流行库。

  2. 加密密码并存储到数据库

    • 在用户注册时,我们使用bcrypt.hash函数对密码进行加密。saltRounds表示生成盐的轮数,值越大,计算越耗时,但安全性也越高。
    • 然后我们将用户名和加密后的密码存储到一个示例数据库中(在这里是一个数组)。
  3. 验证密码

    • 在用户登录时,我们首先查找用户名对应的用户信息。
    • 使用bcrypt.compare函数将用户提供的密码与存储在数据库中的哈希密码进行比较。如果匹配,则表示验证通过。
    • 如果密码验证成功,我们生成一个JWT并返回给客户端。

这种方法确保了即使数据库被泄露,攻击者也无法直接获得用户的明文密码,因为bcrypt生成的哈希值是不可逆的。