三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

彻底搞懂若依RuoYi Token机制:从原理、生活案例到前后端源码全解析(可手写复刻)

彻底搞懂若依RuoYi Token机制:从原理、生活案例到前后端源码全解析(可手写复刻)

彻底搞懂若依RuoYi Token机制:从原理、生活案例到前后端源码全解析(可手写复刻)

前言

做Java前后端分离开发,所有人都在用Token登录校验,尤其是主流框架若依(RuoYi)。

但90%的开发者都只会getToken()、复制拦截器代码,根本不懂整套链路:

Token到底是什么?为什么不用账号密码直接请求?后端怎么生成、怎么校验?前端怎么存储、怎么自动携带、过期怎么处理?

本文不用晦涩术语,结合生活化案例 + 完整后端源码 + 前端全链路源码,从零讲透若依Token核心机制,看完完全可以手写一套独立登录凭证体系,脱离框架也能吃透原理。

一、通俗讲透:什么是Token?为什么必须用Token?

1.1 生活化类比(秒懂核心)

我们可以把 Token = 宾馆房卡,完美对应前后端登录逻辑:

  • 原始登录方式(无Token):每次进门、进房间、消费,都要出示身份证(账号密码),频繁输入、极度繁琐,还存在密码泄露风险。

  • Token登录方式:第一次到前台登记核验身份(登录接口校验账号密码),核验通过后,前台给你一张专属房卡(Token)

  • 后续所有操作:出入所有区域、使用所有服务(调用各类业务接口),只需出示房卡,无需重复出示身份证

  • 过期机制:房卡有固定有效期,过期自动失效,需要重新办理(重新登录/刷新Token),保证账号安全。

1.2 技术底层原因(为什么HTTP必须用Token)

HTTP协议是【无状态协议】,这是所有Token机制的根源:

服务器不会记住你的每一次请求,每一次接口请求都是独立、陌生的,服务器无法识别当前请求来自哪个登录用户。

如果没有Token:

用户每调用一次查询、新增、修改接口,都必须传递账号密码,性能差、安全性极低、用户体验极差。

有了Token:

一次登录,全局免密访问,服务器通过Token识别用户身份、权限、账号信息。

1.3 Session 和 Token 的核心区别(若依为什么选Token)

  • Session:身份数据存在服务端,服务器需要存储所有用户会话,集群部署、分布式场景压力大,不适合前后端分离。

  • JWT Token(若依采用):用户身份、权限、过期时间加密存在Token字符串本身,服务端无需存储会话,只需要解密校验,轻量化、适配分布式、适配前后端分离架构。

二、若依Token完整工作链路(总览)

一套完整的登录认证流程,闭环五步:

  1. 后端:登录接口校验账号密码,合法则生成JWT Token返回前端

  2. 前端:接收Token,本地持久化存储(localStorage)+ 内存缓存(状态管理)

  3. 前端拦截器:所有接口请求自动携带Token到请求头

  4. 后端拦截器:拦截所有请求,解析、校验Token真伪、是否过期

  5. 响应拦截器:捕获Token过期(401),自动清空凭证、跳转登录页

三、后端源码解析(若依核心简化版)

剔除冗余业务代码,只保留Token核心逻辑,可直接看懂底层原理。

3.1 登录接口:生成Token(发证)

核心逻辑:验密 → 生成JWT → 返回前端

/*** 登录接口:账号密码登录,返回Token凭证*/
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {// 1. 数据库校验用户名、密码是否正确SysUser user = userService.checkLogin(loginBody.getUsername(), loginBody.getPassword());// 2. JWT工具类生成Token(核心:签发身份凭证)// 携带用户ID、用户名、过期时间,加密生成唯一字符串String token = JwtUtils.createToken(user.getUserId(), user.getUserName());// 3. 返回Token给前端return AjaxResult.success("登录成功").put("token", token);
}

3.2 全局拦截器:校验所有请求的Token(检票)

所有业务接口,必须经过拦截器校验,无Token/Token失效直接拒绝访问,返回401。

/*** JWT全局拦截器:统一校验用户身份*/
@Component
public class JwtInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从请求头获取Token(固定格式:Bearer 开头)String header = request.getHeader("Authorization");// 2. 判断Token是否存在、格式是否正确if (header != null && header.startsWith("Bearer ")) {// 截取有效Token(去掉Bearer+空格前缀)String token = header.substring(7);try {// 3. 解密Token,校验是否过期、是否合法Claims claims = JwtUtils.parseToken(token);// 4. 解析用户信息,存入当前线程,全局业务可直接获取登录用户Long userId = Long.valueOf(claims.getSubject());UserThreadContext.setUserId(userId);// 校验通过,放行接口请求return true;} catch (Exception e) {// Token过期、篡改、解析失败response.setStatus(401);return false;}}// 无Token,未登录,拒绝访问response.setStatus(401);return false;}
}

3.3 后端核心要点

  • 所有身份识别,不靠Session,只靠Token解密

  • 401状态码 = 凭证失效,是前后端约定的统一过期标识

  • Token自带用户信息、过期时间,服务端无需存储任何会话数据

四、前端源码解析(若依Vue完整链路)

前端核心三件套:Token工具类 + 登录存储 + 请求拦截带参 + 响应拦截过期处理

4.1 Token工具类(auth.js):凭证存取核心

负责Token的存储、读取、删除,是整个前端登录体系的基础。

// 存储Token的key(与若依框架一致)
const TOKEN_KEY = "Admin-Token"// 保存Token:持久化到本地,刷新页面不丢失
export function setToken(token) {localStorage.setItem(TOKEN_KEY, token)
}// 获取Token:每次请求读取凭证
export function getToken() {return localStorage.getItem(TOKEN_KEY)
}// 删除Token:退出登录/凭证过期,清空身份
export function removeToken() {localStorage.removeItem(TOKEN_KEY)
}

关键知识点:Vuex/Pinia是内存存储,刷新页面会清空,Token必须存在localStorage做持久化,不能只存在状态管理中。

4.2 登录页面逻辑:获取并存储Token

async function handleLogin() {// 1. 组装账号密码参数const res = await loginApi({username: loginForm.username,password: loginForm.password})// 2. 从后端返回数据中拿到Tokenconst token = res.data.token// 3. 双重存储:本地持久化 + 内存状态缓存setToken(token)userStore.setToken(token)// 4. 登录成功,跳转首页router.push("/")
}

4.3 请求拦截器(request):自动携带Token

核心功能:不用每个接口手动传Token,所有请求自动带凭证

// axios请求拦截
service.interceptors.request.use(config => {const token = getToken()// 如果存在登录凭证,自动塞入请求头if (token) {// 固定标准格式:Bearer + 空格 + Tokenconfig.headers.Authorization = `Bearer ${token}`}return config
})

高频坑点Bearer 后面必须有空格,少一个空格,后端直接解析失败,报401。

4.4 响应拦截器(response):处理Token过期

后端返回401,代表房卡过期/无效,前端清空身份、退回登录页。

service.interceptors.response.use(res => res,err => {// 捕获未登录/凭证过期if (err.response?.status === 401) {// 清空本地所有登录凭证removeToken()userStore.resetUser()// 跳转登录页,强制重新登录router.push("/login")}return Promise.reject(err)}
)

五、进阶难点:Token刷新机制(续卡逻辑)

5.1 生活化理解

房卡(AccessToken)有效期很短,为了不让用户频繁掉线重登,系统提供一张换卡凭证(RefreshToken)

房卡快过期时,用换卡凭证去前台换新房卡,无需重新输入账号密码,无感续期。

5.2 技术逻辑

  • AccessToken:短期有效(业务请求凭证,安全性高)

  • RefreshToken:长期有效(仅用于刷新新Token,不参与业务请求)

执行流程:

  1. AccessToken过期,接口返回401

  2. 前端拦截到401,携带RefreshToken调用刷新接口

  3. 后端校验RefreshToken合法,返回全新AccessToken

  4. 前端更新本地Token,重新发起失败的业务请求

  5. RefreshToken过期 → 直接踢回登录页

六、开发者高频踩坑总结(必看)

  1. Bearer空格问题:90%的Token报错都是少了空格,前后端格式必须完全统一

  2. 存储位置错误:只存在Pinia/Vuex,刷新页面Token丢失,必须搭配localStorage持久化

  3. JWT无状态特性:Token一旦签发,后端无法主动作废,只能等待过期;强制下线需要借助Redis黑名单

  4. 并发401问题:多个接口同时过期,会重复调用刷新Token接口,需要加锁防抖

  5. 混淆401/403:401=未登录/凭证失效,403=已登录但权限不足

七、全文总结

若依的Token机制,本质就是一套标准化的身份凭证收发体系

账号密码登录(核验身份)→ 后端签发Token(发房卡)→ 前端持久化存储(带房卡)→ 每次请求自动携带房卡 → 后端统一检票校验 → 过期自动清退

看懂这套逻辑,就彻底脱离了“只会CV代码”的阶段,完全可以手写一套独立的登录认证框架,适配所有前后端分离项目。

(注:部分内容可能由 AI 生成)

← 返回列表