为了账号安全,请及时绑定邮箱和手机立即绑定

JWT入门:轻松掌握JSON Web Tokens

概述

JWT入门介绍了JSON Web Tokens (JWT) 这一紧凑且安全的信息传输标准,适用于身份验证和授权过程。文章详细解释了JWT的工作原理、组成部分及其优势应用场景,并提供了创建和验证JWT的示例代码。JWT的存储方式和安全性也得到了深入探讨,确保了在实际项目中的有效应用。

1. JWT简介

什么是JWT

JSON Web Tokens (JWT) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息。它以紧凑的形式携带声明,通常用于身份验证和授权过程。JWT 由三部分组成,每部分之间由点(.)分隔:头部、载荷和签名。

JWT 典型格式如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT的工作原理

JWT 的核心工作流程包括创建、签名、验证和解码。具体步骤如下:

  1. 创建JWT:客户端向服务器发送请求,服务器根据认证信息(如用户名和密码)生成 JWT。
  2. 签名JWT:服务器使用密钥(通常是私钥)对 JWT 进行签名,确保 JWT 的完整性和防篡改。
  3. 传输JWT:JWT 被传输到客户端,客户端可以将其存储在本地(如 localStorage 或 session storage)。
  4. 验证JWT:当客户端需要访问受保护资源时,它会将 JWT 附加到请求的 Authorization 头中。服务器收到请求后,验证 JWT 的有效性,包括签名和过期时间等。
  5. 解码JWT:服务器解析 JWT 载荷中的信息,使用这些信息来决定如何处理请求。

JWT的优势与应用场景

JWT 的优势包括:

  • 紧凑性:JWT 是一种紧凑的格式,适合通过 URL、POST 请求体、Cookie 等方式传输。
  • 无状态:服务器不需要存储 JWT 的信息,减轻了数据库负担。
  • 签名:JWT 可以被签名,从而提高了安全性。
  • 自带信息:JWT 包含了用户信息,服务器可以通过解析 JWT 来获取必要的用户信息。

JWT 的应用场景包括:

  • 用户认证:使用 JWT 进行用户认证,实现无状态的认证方案。
  • 跨域共享:JWT 可以轻松地在多个域之间共享用户身份。
  • 单点登录:实现单点登录(SSO),用户在一个应用登录后,其他应用也可以通过 JWT 进行认证。
  • 资源访问控制:通过解析 JWT 来控制用户对资源的访问权限。

2. JWT的组成部分

JWT 由三部分组成:头部、载荷和签名。每部分都有特定的作用。

头部(Header)

头部包含两个部分,分别是令牌的类型(JWT)和所使用的签名算法(例如 HMAC SHA256 或 RSA)。头部采用 Base64 编码。

示例头部:

{
  "alg": "HS256",
  "typ": "JWT"
}

载荷(Payload)

载荷是 JWT 的主体部分,包含声明(claims)。声明是关于实体(通常为用户)和其他数据的声明。例如,sub 是用户的身份,iat 是声明生成的时间。

示例载荷:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

签名(Signature)

签名用于确保 JWT 的完整性和防篡改。签名是通过使用加密算法(例如 HMAC SHA256 )和头部中指定的密钥来生成的。

示例签名:

HMACSHA256(
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
  "secret",
  "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
)

生成的 JWT 字符串如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

3. 创建JWT

选择算法

选择合适的加密算法对 JWT 的安全性至关重要。常见的算法包括:

  • HMAC SHA256:使用密钥生成签名,适用于服务器到服务器的通信。
  • RS256:使用 RSA 加密算法生成签名,适用于公钥/私钥场景。

示例代码(使用 HMAC SHA256):

const jwt = require('jsonwebtoken');

const token = jwt.sign(
  {
    sub: '1234567890',
    name: 'John Doe',
    admin: true,
    iat: Math.floor(Date.now() / 1000)
  },
  'shhhh',
  {
    algorithm: 'HS256'
  }
);
console.log(token);

生成JWT字符串

使用选择的算法生成 JWT 字符串,包含头部、载荷和签名三个部分。

示例代码(使用 HMAC SHA256):

const jwt = require('jsonwebtoken');
const payload = {
  sub: '1234567890',
  name: 'John Doe',
  admin: true,
  iat: Math.floor(Date.now() / 1000)
};
const secret = 'shhhh';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });
console.log(token);

使用工具快速创建JWT

使用在线工具快速创建 JWT,如 jwt.io。在该网站中,你可以选择算法,输入头部、载荷和密钥,自动生成 JWT 字符串。

4. 验证JWT

验证签名

签名验证确保 JWT 的完整性和防篡改。服务器使用相同的密钥来验证签名。

示例代码:

const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
try {
  const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
  console.log(decoded);
} catch (err) {
  console.log('Invalid token!');
}

验证过期时间

JWT 可以包含过期时间(exp),服务器需要验证 JWT 是否在有效期内。

示例代码(包含过期时间):

const jwt = require('jsonwebtoken');
const payload = {
  sub: '1234567890',
  name: 'John Doe',
  admin: true,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiration
};
const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
console.log(token);

验证令牌的有效性

除了验证签名和过期时间,还需要确保 JWT 在传输过程中没有被篡改或损坏。

示例代码:

const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
try {
  const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
  console.log(decoded);
} catch (err) {
  console.log('Invalid token!');
}

验证过期时间示例(已过期)

const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
const payload = {
  sub: '1234567890',
  name: 'John Doe',
  admin: true,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) - 60 // 已过期
};
const token_expired = jwt.sign(payload, secret, { algorithm: 'HS256' });

try {
  const decoded = jwt.verify(token_expired, secret, { algorithms: ['HS256'] });
  console.log(decoded);
} catch (err) {
  console.log('Token has expired!');
}

5. 使用JWT实现用户认证

前端请求JWT

前端可以通过表单提交用户名和密码,服务器验证用户信息后返回 JWT。

示例代码(前端):

fetch('/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    username: 'john.doe',
    password: 'secret'
  })
})
.then(response => response.json())
.then(data => {
  console.log(data.token);
})
.catch(error => console.error('Error:', error));

后端验证JWT

后端收到登录请求后,验证用户信息,生成 JWT 并返回给前端。

示例代码(后端):

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 模拟用户验证
  if (username === 'john.doe' && password === 'secret') {
    const payload = {
      sub: '1234567890',
      name: 'John Doe',
      admin: true,
      iat: Math.floor(Date.now() / 1000)
    };
    const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
    res.json({ token });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

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

使用JWT进行身份验证

前端在每次请求受保护资源时,需要将 JWT 附加到请求头中。

示例代码(前端):

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

fetch('/protected', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.json())
.then(data => {
  console.log(data);
})
.catch(error => console.error('Error:', error));

示例代码(后端):

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());

app.get('/protected', (req, res) => {
  const token = req.header('Authorization').split(' ')[1];
  try {
    const decoded = jwt.verify(token, 'shhhh', { algorithms: ['HS256'] });
    console.log(decoded);
    res.json({ message: 'Access granted' });
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 模拟用户验证
  if (username === 'john.doe' && password === 'secret') {
    const payload = {
      sub: '1234567890',
      name: 'John Doe',
      admin: true,
      iat: Math.floor(Date.now() / 1000)
    };
    const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
    res.json({ token });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

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

6. JWT存储与安全性

JWT的存储方式

JWT 可以存储在本地(如 localStorage 或 session storage),也可以存储在 cookie 中。选择合适的存储方式取决于具体需求。

安全存储JWT的方法

  • HTTPS:确保所有通信都是通过 HTTPS 进行的,防止中间人攻击。
  • Access-Control-Allow-Origin:设置合适的 CORS 头,限制 JWT 的来源。
  • HttpOnly:如果存储在 cookie 中,设置 HttpOnly 标志防止 JavaScript 访问。
  • Secure:如果存储在 cookie 中,设置 Secure 标志,确保只在 HTTPS 连接中使用。

防止JWT被劫持和重放攻击

  • 刷新令牌:实现刷新令牌机制,定期更新 JWT,防止长期有效的 JWT 被劫持。
  • 有效期限制:设置合理的过期时间,限制 JWT 的有效期。
  • 令牌黑名单:维护一个已使用或失效的 JWT 黑名单,防止重放攻击。

示例代码(刷新令牌):

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());

let refreshToken = '';

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // 模拟用户验证
  if (username === 'john.doe' && password === 'secret') {
    const payload = {
      sub: '1234567890',
      name: 'John Doe',
      admin: true,
      iat: Math.floor(Date.now() / 1000)
    };
    const accessToken = jwt.sign(payload, 'accessSecret', { algorithm: 'HS256' });
    const refreshTokenPayload = {
      sub: '1234567890',
      name: 'John Doe',
      admin: true,
      iat: Math.floor(Date.now() / 1000)
    };
    refreshToken = jwt.sign(refreshTokenPayload, 'refreshSecret', { algorithm: 'HS256' });
    res.json({ accessToken, refreshToken });
  } else {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

app.post('/refreshToken', (req, res) => {
  const { refreshToken } = req.body;
  try {
    jwt.verify(refreshToken, 'refreshSecret', { algorithms: ['HS256'] });
    const payload = {
      sub: '1234567890',
      name: 'John Doe',
      admin: true,
      iat: Math.floor(Date.now() / 1000)
    };
    const accessToken = jwt.sign(payload, 'accessSecret', { algorithm: 'HS256' });
    res.json({ accessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

app.get('/protected', (req, res) => {
  const token = req.header('Authorization').split(' ')[1];
  try {
    const decoded = jwt.verify(token, 'accessSecret', { algorithms: ['HS256'] });
    console.log(decoded);
    res.json({ message: 'Access granted' });
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
});

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

通过上述步骤,你可以轻松掌握 JSON Web Tokens 的使用方法,并在实际项目中实现用户认证和授权等功能。希望这篇文章对你有所帮助!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消