Modules

认证模块

认证与授权模块文档,详细说明JWT认证流程。

认证 (Auth) 模块负责系统的用户认证、授权以及会话管理,基于 JWT (JSON Web Token) 实现无状态认证。

1. 认证模块概述

认证模块是系统的安全核心,负责验证用户身份并授予访问权限。Tagtag Starter 认证模块采用现代化的 JWT 认证机制,实现了无状态认证,提高了系统的可扩展性和性能。

1.1 核心功能

  • 用户登录与登出
  • JWT 令牌生成与验证
  • 令牌刷新机制
  • 权限校验
  • 会话管理

1.2 技术栈

  • JWT: JSON Web Token,用于无状态认证
  • BCrypt: 密码加密算法,确保密码安全
  • Spring Security: 安全框架,提供认证和授权功能
  • Redis: 用于缓存令牌黑名单和会话信息

2. JWT 认证原理

2.1 JWT 基本概念

JWT (JSON Web Token) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。JWT 可以被验证和信任,因为它是数字签名的。

2.2 JWT 结构

JWT 由三部分组成,用点 (.) 分隔:

  • Header: 包含令牌类型和签名算法
  • Payload: 包含声明(claims),如用户ID、角色、过期时间等
  • Signature: 用于验证令牌的完整性

格式: header.payload.signature

2.3 JWT 工作流程

  1. 用户使用用户名和密码登录系统
  2. 服务器验证用户名和密码的正确性
  3. 服务器生成 JWT 令牌并返回给客户端
  4. 客户端存储 JWT 令牌
  5. 客户端在后续请求中携带 JWT 令牌
  6. 服务器验证 JWT 令牌的有效性
  7. 服务器处理请求并返回响应

2.4 JWT 优势

  • 无状态: 服务器不需要存储会话信息,提高了系统的可扩展性
  • 跨域支持: JWT 支持跨域认证,便于构建分布式系统
  • 自包含: 令牌包含了所有必要的用户信息,减少了数据库查询
  • 易于集成: 可以与各种客户端兼容

3. 认证流程

3.1 登录流程

┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│  客户端    │     │  服务器    │     │  数据库    │     │  Redis     │
└─────┬──────┘     └─────┬──────┘     └─────┬──────┘     └─────┬──────┘
      │                  │                  │                  │
      │ 1. 登录请求      │                  │                  │
      │────────────────▶│                  │                  │
      │                  │                  │                  │
      │                  │ 2. 验证用户名密码 │                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │                  │ 3. 生成 JWT 令牌 │                  │
      │                  │◀────────────────│                  │
      │                  │                  │                  │
      │                  │ 4. 存储令牌信息  │                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │ 5. 返回 JWT 令牌 │                  │                  │
      │◀────────────────│                  │                  │
      │                  │                  │                  │
      │                  │                  │                  │

3.2 请求认证流程

┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│  客户端    │     │  服务器    │     │  数据库    │     │  Redis     │
└─────┬──────┘     └─────┬──────┘     └─────┬──────┘     └─────┬──────┘
      │                  │                  │                  │
      │ 1. 请求携带 JWT   │                  │                  │
      │────────────────▶│                  │                  │
      │                  │                  │                  │
      │                  │ 2. 验证 JWT 有效性│                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │                  │ 3. 检查令牌黑名单│                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │                  │ 4. 解析用户信息  │                  │
      │                  │◀────────────────│                  │
      │                  │                  │                  │
      │                  │ 5. 权限校验      │                  │
      │                  │                  │                  │
      │                  │ 6. 处理请求      │                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │ 7. 返回响应      │◀────────────────│                  │
      │◀────────────────│                  │                  │
      │                  │                  │                  │

3.3 令牌刷新流程

┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐
│  客户端    │     │  服务器    │     │  数据库    │     │  Redis     │
└─────┬──────┘     └─────┬──────┘     └─────┬──────┘     └─────┬──────┘
      │                  │                  │                  │
      │ 1. 刷新令牌请求   │                  │                  │
      │────────────────▶│                  │                  │
      │                  │                  │                  │
      │                  │ 2. 验证刷新令牌  │                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │                  │ 3. 生成新 JWT   │                  │
      │                  │◀────────────────│                  │
      │                  │                  │                  │
      │                  │ 4. 存储新令牌信息│                  │
      │                  │────────────────▶│                  │
      │                  │                  │                  │
      │ 5. 返回新 JWT     │                  │                  │
      │◀────────────────│                  │                  │
      │                  │                  │                  │

4. 核心功能实现

4.1 用户登录

功能: 验证用户身份并生成 JWT 令牌。

实现细节:

  • 密码验证:使用 BCrypt 算法验证密码
  • 令牌生成:生成访问令牌和刷新令牌
  • 令牌存储:将令牌信息存储到 Redis
  • 响应格式:返回令牌、用户信息和权限列表

登录请求:

POST /auth/login
Content-Type: application/json

{
  "username": "admin",
  "password": "admin123"
}

登录响应:

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tokenType": "Bearer",
    "expiresIn": 3600
  }
}

4.2 令牌验证

功能: 验证 JWT 令牌的有效性。

实现细节:

  • 签名验证:验证令牌的签名是否有效
  • 过期验证:检查令牌是否过期
  • 黑名单验证:检查令牌是否在黑名单中
  • 权限验证:验证用户是否拥有访问权限

实现代码:

@Component
public class JwtProvider {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private long expiration;
    
    public Claims getClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secret.getBytes(StandardCharsets.UTF_8))
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    public boolean validateToken(String token) {
        try {
            getClaimsFromToken(token);
            return !isTokenExpired(token);
        } catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
            return false;
        }
    }
    
    private boolean isTokenExpired(String token) {
        Claims claims = getClaimsFromToken(token);
        Date expirationDate = claims.getExpiration();
        return expirationDate.before(new Date());
    }
}

4.3 令牌刷新

功能: 使用刷新令牌生成新的访问令牌。

实现细节:

  • 刷新令牌验证:验证刷新令牌的有效性
  • 生成新令牌:生成新的访问令牌
  • 更新令牌信息:更新 Redis 中的令牌信息
  • 过期旧令牌:将旧令牌加入黑名单

刷新请求:

POST /auth/refresh
Content-Type: application/json

{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

4.4 用户登出

功能: 退出登录并使令牌失效。

实现细节:

  • 令牌黑名单:将令牌加入黑名单
  • 清除缓存:清除用户相关缓存
  • 会话失效:使当前会话失效

登出请求:

POST /auth/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

5. 核心代码结构

5.1 后端代码结构

tagtag-module-auth
└── src
    └── main
        ├── java
        │   └── dev
        │       └── tagtag
        │           └── module
        │               └── auth
        │                   ├── controller
        │                   │   └── AuthController.java
        │                   ├── entity
        │                   │   └── AuthUser.java
        │                   ├── service
        │                   │   ├── AuthService.java
        │                   │   └── impl
        │                   │       └── AuthServiceImpl.java
        │                   ├── util
        │                   │   ├── JwtUtil.java
        │                   │   └── PasswordUtil.java
        │                   └── vo
        │                       ├── LoginRequest.java
        │                       ├── LoginResponse.java
        │                       └── RefreshTokenRequest.java
        └── resources
            └── db
                ├── schema.sql
                └── data
                    └── init.sql

5.2 前端代码结构

frontend/apps/tagtag/src/api/core
├── auth.ts       # 认证相关 API
├── captcha.ts    # 验证码相关 API
└── index.ts      # API 入口

6. 前端实现

6.1 登录页面

功能: 提供用户登录界面,支持用户名密码登录和验证码登录。

实现细节:

  • 表单验证:验证用户名和密码格式
  • 验证码支持:防止暴力破解
  • 记住密码:可选功能,使用 localStorage 存储
  • 登录状态管理:使用 Pinia 管理登录状态

代码示例:

<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthStore } from '@/store/auth';
import { loginApi } from '@/api/core/auth';

const router = useRouter();
const authStore = useAuthStore();

const formData = reactive({
  username: '',
  password: '',
  captcha: '',
  rememberMe: false
});

const loading = ref(false);

async function handleLogin() {
  try {
    loading.value = true;
    const result = await loginApi(formData);
    authStore.setToken(result.accessToken);
    router.push('/');
  } catch (error) {
    console.error('登录失败:', error);
  } finally {
    loading.value = false;
  }
}
</script>

<template>
  <div class="login-container">
    <div class="login-form">
      <h2>Tagtag Starter 登录</h2>
      <Form :model="formData" @submit.prevent="handleLogin">
        <FormItem label="用户名" required>
          <Input v-model:value="formData.username" placeholder="请输入用户名" />
        </FormItem>
        <FormItem label="密码" required>
          <Input.Password v-model:value="formData.password" placeholder="请输入密码" />
        </FormItem>
        <FormItem label="验证码" required>
          <Input v-model:value="formData.captcha" placeholder="请输入验证码" />
          <img class="captcha-img" src="/api/captcha" @click="refreshCaptcha" />
        </FormItem>
        <FormItem>
          <Checkbox v-model:checked="formData.rememberMe">记住密码</Checkbox>
        </FormItem>
        <FormItem>
          <Button type="primary" html-type="submit" :loading="loading" block>
            登录
          </Button>
        </FormItem>
      </Form>
    </div>
  </div>
</template>

6.2 认证状态管理

功能: 管理用户登录状态,包括令牌存储、用户信息存储和权限管理。

实现细节:

  • 令牌存储:使用 localStorage 或 cookie 存储令牌
  • 用户信息缓存:缓存用户信息,减少请求次数
  • 权限管理:根据用户权限控制菜单和按钮显示
  • 自动刷新令牌:在令牌过期前自动刷新

代码示例:

import { defineStore } from 'pinia';
import { loginApi, logoutApi, refreshTokenApi } from '@/api/core/auth';

export const useAuthStore = defineStore('auth', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    refreshToken: localStorage.getItem('refreshToken') || '',
    userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null'),
    permissions: JSON.parse(localStorage.getItem('permissions') || '[]'),
    roles: JSON.parse(localStorage.getItem('roles') || '[]')
  }),
  actions: {
    setToken(token: string) {
      this.token = token;
      localStorage.setItem('token', token);
    },
    setRefreshToken(refreshToken: string) {
      this.refreshToken = refreshToken;
      localStorage.setItem('refreshToken', refreshToken);
    },
    setUserInfo(userInfo: any) {
      this.userInfo = userInfo;
      this.permissions = userInfo.permissions || [];
      this.roles = userInfo.roles || [];
      localStorage.setItem('userInfo', JSON.stringify(userInfo));
      localStorage.setItem('permissions', JSON.stringify(this.permissions));
      localStorage.setItem('roles', JSON.stringify(this.roles));
    },
    async login(loginData: any) {
      const result = await loginApi(loginData);
      this.setToken(result.accessToken);
      this.setRefreshToken(result.refreshToken);
      return result;
    },
    async logout() {
      await logoutApi();
      this.clearAuth();
    },
    async refreshToken() {
      const result = await refreshTokenApi({ refreshToken: this.refreshToken });
      this.setToken(result.accessToken);
      this.setRefreshToken(result.refreshToken);
      return result;
    },
    clearAuth() {
      this.token = '';
      this.refreshToken = '';
      this.userInfo = null;
      this.permissions = [];
      this.roles = [];
      localStorage.removeItem('token');
      localStorage.removeItem('refreshToken');
      localStorage.removeItem('userInfo');
      localStorage.removeItem('permissions');
      localStorage.removeItem('roles');
    }
  }
});

6.3 请求拦截器

功能: 在请求中自动携带 JWT 令牌,处理令牌过期和刷新。

实现细节:

  • 自动添加令牌:在请求头中添加 Authorization 头
  • 令牌过期处理:捕获 401 错误,尝试刷新令牌
  • 刷新令牌失败:跳转到登录页面

代码示例:

import axios from 'axios';
import { useAuthStore } from '@/store/auth';

const request = axios.create({
  baseURL: import.meta.env.VITE_APP_API_BASE_URL,
  timeout: 10000
});

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore();
    if (authStore.token) {
      config.headers.Authorization = `Bearer ${authStore.token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    return response.data;
  },
  async (error) => {
    const authStore = useAuthStore();
    const originalRequest = error.config;
    
    // 处理 401 错误
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        // 尝试刷新令牌
        await authStore.refreshToken();
        // 使用新令牌重试请求
        originalRequest.headers.Authorization = `Bearer ${authStore.token}`;
        return request(originalRequest);
      } catch (refreshError) {
        // 刷新令牌失败,跳转到登录页面
        authStore.clearAuth();
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

export default request;

7. API 参考

方法端点描述
POST/auth/login用户登录
POST/auth/logout用户登出
POST/auth/refresh刷新令牌
GET/auth/me获取当前用户信息
GET/auth/permissions获取当前用户权限
GET/auth/roles获取当前用户角色
GET/captcha获取验证码

8. 最佳实践

8.1 安全建议

  • 使用 HTTPS: 在生产环境中使用 HTTPS 协议,确保数据传输安全
  • 强密码策略: 强制用户使用强密码,定期更换密码
  • 令牌过期时间: 设置合理的令牌过期时间,减少风险
  • 刷新令牌机制: 实现刷新令牌机制,提高用户体验
  • 令牌黑名单: 实现令牌黑名单,支持主动使令牌失效
  • 防止 CSRF 攻击: 实现 CSRF 防护机制
  • 防止 XSS 攻击: 对用户输入进行转义,防止 XSS 攻击

8.2 性能优化

  • 令牌缓存: 缓存令牌,减少数据库查询
  • 减少令牌大小: 只在令牌中包含必要的信息
  • 异步验证: 异步验证令牌,提高系统性能
  • 批量权限检查: 批量检查权限,减少请求次数

8.3 可扩展性

  • 模块化设计: 认证模块与业务模块分离,便于扩展
  • 支持多种认证方式: 支持用户名密码登录、短信登录、第三方登录等
  • 支持多租户: 支持多租户认证
  • 支持 OAuth 2.0: 支持 OAuth 2.0 协议

9. 扩展建议

9.1 支持多种登录方式

  • 短信登录: 支持短信验证码登录
  • 邮箱登录: 支持邮箱验证码登录
  • 第三方登录: 支持微信、QQ、GitHub 等第三方登录
  • 生物识别登录: 支持指纹、面部识别等生物识别登录

9.2 增强安全机制

  • 多因素认证: 支持多因素认证,提高安全性
  • 风险控制: 实现登录风险控制,防止暴力破解
  • 审计日志: 记录认证相关的审计日志
  • 异常检测: 检测异常登录行为

9.3 支持 SSO

实现单点登录 (SSO),支持多系统间的无缝登录。

9.4 支持 JWT 扩展

  • 支持 JWT 扩展声明: 支持自定义声明
  • 支持 JWT 加密: 支持 JWT 加密,提高安全性
  • 支持 JWT 吊销: 支持 JWT 吊销机制

10. 总结

认证模块是 Tagtag Starter 系统的安全核心,基于 JWT 实现了无状态认证机制。该模块提供了完整的认证功能,包括用户登录、令牌验证、令牌刷新和用户登出等。

通过认证模块,系统实现了统一的身份验证和授权机制,确保只有授权用户才能访问系统资源。同时,认证模块支持多种扩展方式,可以根据业务需求灵活扩展。

认证模块的设计和实现遵循了安全性、性能和可扩展性原则,为系统提供了可靠的安全保障。