JWT

96 阅读5分钟

JSON web Token 通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输、 信息传输、授权

  • JWT的认证流程:
  1. 前端把账号密码发送给后端的接口
  2. 后端核对账号密码成功后,把用户id等其他信息作为JWT负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT(token)。
  3. 客户端将JWT存储在本地,每次请求时都会把JWT放在HTTP请求头的Authorization字段内
  4. 后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期)
  5. 验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果

一、知识点描述

1. JWT工作机制

  • 基于Token的无状态认证协议

  • JWT由三部分组成(Header.Payload.Signature):

    • Header:声明令牌类型(JWT)及签名算法(如HS256RSA),经Base64编码。
    • Payload:包含用户信息(如用户ID、角色)及标准声明(exp过期时间、iat签发时间等),同样Base64编码。
    • Signature:使用密钥对编码后的Header和Payload进行签名,确保数据完整性
  • 生命周期:生成(服务端签发)→ 传输(客户端存储)→ 验证(签名校验)

2. 路由守卫(AOP控制)

  • 前端路由层面的切面控制
  • 通过路由meta字段标记权限需求
  • 导航守卫自动检查登录状态并重定向

3. Axios拦截器

  • 请求拦截器:自动附加Authorization头
  • 响应拦截器:集中处理401等认证异常
  • 统一错误处理管道,避免重复代码

4. 安全实践

  • 敏感信息不存储于JWT(使用userId而非完整对象)
  • HTTPS传输保障
  • 客户端安全存储(localStorage/SessionStorage)
  • 服务端密钥分离管理

二、使用缘由与优势

1. 为什么使用JWT?

  • 解决传统Session问题: 消除服务端Session存储,天然支持分布式系统,避免集群同步问题
  • 跨域支持友好: 通过简单添加Authorization头即可实现跨域认证,优于Cookie方案
  • 信息自包含: Payload可携带非敏感用户信息,减少数据库查询次数

2. 为什么需要路由守卫?

  • 集中访问控制: 避免在每个页面组件中重复编写权限校验逻辑
  • 无缝跳转体验: 自动记录原始请求路径(redirect参数),登录后自动跳转目标页
  • 防御性编程: 防止未授权用户通过直接URL访问受保护页面

3. 为什么封装Axios拦截器?

  • 降低耦合度: 认证逻辑与业务组件解耦,修改认证策略只需调整一处
  • 异常处理标准化: 统一处理网络错误、超时、Token过期等场景,提升代码健壮性
  • 简化开发: 自动携带凭证,开发者无需在每个请求中手动添加Token

4. 安全设计的意义

  • 防篡改: Signature签名机制防止Token被恶意修改
  • 防泄露: 短期有效期(2h)+ 客户端安全存储降低被盗风险
  • 防CSRF: 相比Cookie方案,Header携带方式天然免疫CSRF攻击

三、方案核心价值

  1. 提升系统扩展性 无状态特性使服务端容易水平扩展,适合微服务架构
  2. 优化用户体验 401自动跳转、凭证自动续传等机制让用户无感知完成认证流程
  3. 降低维护成本 模块化设计使认证逻辑与业务代码分离,升级维护只需修改独立模块
  4. 符合现代开发趋势 前后端完全分离,前端应用(SPA/移动端)可复用同一认证接口

JWT认证系统集成方案

一、服务端实现(Node.js/Express)

1. 依赖安装

npm install express jsonwebtoken cors body-parser

2. JWT工具类(utils/jwtUtil.js)

const jwt = require('jsonwebtoken');
const secret = 'your-secret-key'; // 生产环境建议使用环境变量

module.exports = {
  // 生成Token(有效期为2小时)
  generateToken: (user) => {
    return jwt.sign(
      { userId: user.id, username: user.username },
      secret,
      { expiresIn: '2h' }
    );
  },

  // 验证中间件
  verifyToken: (req, res, next) => {
    const token = req.headers.authorization?.split(' ');
    
    if (!token) return res.status(401).json({ message: '未提供认证令牌' });

    jwt.verify(token, secret, (err, decoded) => {
      if (err) {
        const message = err.name === 'TokenExpiredError' 
          ? '令牌已过期' 
          : '无效的认证令牌';
        return res.status(401).json({ message });
      }
      req.user = decoded;
      next();
    });
  }
};

3. 路由示例(routes/auth.js)

const router = require('express').Router();
const jwtUtil = require('../utils/jwtUtil');

// 登录接口
router.post('/login', (req, res) => {
  // 验证用户凭证(示例代码,需完善实际验证逻辑)
  const { username, password } = req.body;
  const user = { id: 1, username: 'demo' }; // 模拟用户数据
  
  // 生成Token
  const token = jwtUtil.generateToken(user);
  
  res.json({ 
    token,
    user: { id: user.id, username: user.username }
  });
});

// 受保护接口
router.get('/profile', jwtUtil.verifyToken, (req, res) => {
  res.json({ user: req.user });
});

module.exports = router;

二、前端实现(Vue/React)

1. Axios实例配置(src/api/axios.js)

import axios from 'axios';

const service = axios.create({
  baseURL: process.env.VUE_APP_API_URL,
  timeout: 10000
});

// 请求拦截器(添加Token)
service.interceptors.request.use(config => {
  const token = localStorage.getItem('jwt_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

// 响应拦截器(处理异常)
service.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response.status === 401) {
      // Token失效处理
      localStorage.removeItem('jwt_token');
      window.location.href = '/login'; // 跳转登录页
    }
    return Promise.reject(error.response?.data || '请求失败');
  }
);

export default service;

2. 路由守卫(Vue Router示例)

router.beforeEach((to, from, next) => {
  const isAuthenticated = !!localStorage.getItem('jwt_token');
  
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isAuthenticated) {
      next({ path: '/login', query: { redirect: to.fullPath } });
    } else {
      next();
    }
  } else {
    next();
  }
});

3. 登录组件示例

import api from '../api/axios';

export default {
  methods: {
    async handleLogin() {
      try {
        const res = await api.post('/login', {
          username: this.username,
          password: this.password
        });
        
        localStorage.setItem('jwt_token', res.token);
        // 跳转到首页或目标页面
        this.$router.push(this.$route.query.redirect || '/');
      } catch (err) {
        console.error('登录失败:', err);
      }
    }
  }
}

三、使用说明

  1. 服务端配置:

    • 替换your-secret-key为强密钥(推荐使用环境变量)
    • 添加CORS配置(开发环境)
    JavaScript
    app.use(cors({
      origin: process.env.CLIENT_URL,
      credentials: true
    }));
    
  2. 前端配置:

    • 在.env文件中配置接口地址
     VUE_APP_API_URL=http://your-api-domain.com
    
    • 受保护路由配置meta字段
    JavaScript
    {
      path: '/dashboard',
      component: Dashboard,
      meta: { requiresAuth: true }
    }
    
  3. 安全增强建议:

    • 启用HTTPS
    • 添加Refresh Token机制
    • 设置适当的Token过期时间
    • 使用HttpOnly Cookie存储Token(需调整CORS配置)

四、异常处理流程

  1. 401未授权:

    • 自动清除本地Token
    • 重定向到登录页
    • 携带原始请求路径参数
  2. Token过期:

    • 服务端返回标准错误格式
    • 前端自动触发登出流程
  3. 网络错误:

    • 统一处理并显示错误提示
    • 保留最后请求状态便于重试