若依脚手架详解二_SpringSecurity_数据字典_框架核心

O泡李华 9

若依脚手架详解(二):Spring Security、数据字典与框架核心

本章位置:第三阶段 Java 企业项目 + AI 助手
对应课程:使用 AI 了解若依脚手架(Security、数据字典)
前置知识:若依脚手架(一)、SpringBoot、Spring Security 基础、Redis、RBAC、AOP、MyBatis、Vue3、Pinia
后续衔接:淘车湾项目、若依脚手架改造、SpringCloud 若依 Cloud
学习目标:真正看懂若依框架层,掌握 SecurityFilterChain、JWT 认证过滤器、SecurityContext、LoginUser、401/403、匿名访问、权限判断、数据权限、数据字典、防重复提交、XSS、异常处理、操作日志以及前后端核心调用链。


一、本章和上一章有什么区别

上一章重点:

若依是什么

模块结构

登录主线

RBAC 表关系

动态菜单

POI Excel

这一章开始进入:

框架底层

你要解决:

请求为什么能被 Security 拦住?

JWT Filter 什么时候执行?

SecurityContext 里到底放了什么?

401 和 403 为什么不同?

@PreAuthorize 为什么有效?

@Anonymous 怎么放行?

@DataScope 怎么给 SQL 加范围?

数据字典为什么不用 if/else?

useDict 是怎么拿字典数据的?

@RepeatSubmit 怎么防重复点击?

XSS Filter 到底过滤什么?

GlobalExceptionHandler 怎么统一异常?

@Log 为什么一个注解就能记录操作日志?

二、若依框架层最重要的文件

阅读时优先找:

SecurityConfig

JwtAuthenticationTokenFilter

AuthenticationEntryPointImpl

LogoutSuccessHandlerImpl

TokenService

LoginUser

PermissionService

SecurityUtils

PermitAllUrlProperties

DataScope

DataScopeAspect

RedisCache

GlobalExceptionHandler

RepeatSubmit

RepeatSubmitInterceptor / RepeatSubmitAspect

XssFilter

Log

LogAspect

不同版本:

类名和包路径可能略有差异

但核心职责:

基本一致

三、Spring Boot 3 与旧教程最明显的区别

旧 Spring Security 教程常看到:

extends WebSecurityConfigurerAdapter

新版本:

已经不再以这种方式作为主配置

Spring Boot 3 / 新 Spring Security 主要使用:

@Bean
SecurityFilterChain

并配合:

@EnableMethodSecurity

四、SecurityConfig 是什么

可以把它理解成:

整个 Web 安全系统的总配置

它决定:

哪些 URL 放行

哪些 URL 要登录

认证失败怎么处理

Session 策略

JWT Filter 插在哪里

Logout 怎么处理

CORS Filter 顺序

五、Spring Boot 3 风格 SecurityFilterChain

简化示意:

@Bean
public SecurityFilterChain filterChain(
        HttpSecurity http
) throws Exception {

    http
        .csrf(
            csrf ->
                csrf.disable()
        )
        .sessionManagement(
            session ->
                session.sessionCreationPolicy(
                    SessionCreationPolicy.STATELESS
                )
        )
        .authorizeHttpRequests(
            auth ->
                auth
                    .requestMatchers(
                        "/login",
                        "/register",
                        "/captchaImage"
                    )
                    .permitAll()
                    .anyRequest()
                    .authenticated()
        )
        .addFilterBefore(
            authenticationTokenFilter,
            UsernamePasswordAuthenticationFilter.class
        );

    return http.build();
}

六、requestMatchers

Spring Boot 3 / 新 Spring Security:

requestMatchers

替代旧教程常见:

antMatchers

所以如果你用:

Spring Boot 3

却复制:

Spring Security 5 老代码

经常直接:

编译失败

七、CSRF 为什么常禁用

若依前后端分离:

主要使用 Token

而不是:

传统服务器 Session + 表单

所以常配置:

csrf.disable()

但是不要记成:

“任何项目都要禁用 CSRF”

要看认证方式。


八、STATELESS

SessionCreationPolicy.STATELESS

表示:

Spring Security 不依赖服务器 HttpSession
保存认证状态

认证主要来自:

每个请求携带的 Token

九、这和 Redis Session Token 冲突吗

不冲突。

这里的:

STATELESS

主要指:

Spring Security HttpSession

若依仍可以:

自己通过 Redis 保存 LoginUser

十、请求进入后发生什么

总体:

HTTP Request
↓
Servlet Filter Chain
↓
CorsFilter
↓
JwtAuthenticationTokenFilter
↓
Spring Security
↓
Controller

十一、JwtAuthenticationTokenFilter

这是:

若依认证请求的核心过滤器

经典实现通常继承:

OncePerRequestFilter

十二、OncePerRequestFilter

目的:

保证一次请求中
过滤逻辑通常只执行一次

十三、JWT Filter 核心步骤

1. 从 Request 获取 Token

2. 解析 Token

3. 根据 Token 查询 Redis LoginUser

4. 判断 LoginUser 是否存在

5. 刷新 Token 有效期

6. 创建 Authentication

7. 放入 SecurityContext

8. 继续 FilterChain

十四、伪代码

protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain
) {

    LoginUser loginUser =
            tokenService
                .getLoginUser(
                    request
                );

    if (
        loginUser != null
        &&
        SecurityUtils
            .getAuthentication()
            == null
    ) {

        tokenService
            .verifyToken(
                loginUser
            );

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(
                    loginUser,
                    null,
                    loginUser.getAuthorities()
                );

        SecurityContextHolder
            .getContext()
            .setAuthentication(
                authentication
            );
    }

    filterChain.doFilter(
        request,
        response
    );
}

具体源码:

按当前分支为准

十五、SecurityContextHolder

这是 Spring Security 的核心之一。

它保存:

当前请求线程里的 Authentication

十六、Authentication

表示:

当前认证信息

里面通常有:

Principal

Authorities

Authenticated

十七、Principal

若依里常常是:

LoginUser

十八、Authorities

表示:

Spring Security 权限集合

但若依自己的:

PermissionService

还会直接使用:

LoginUser.permissions

十九、SecurityContext 建立成功意味着什么

后面的 Controller:

知道“当前是谁”

@PreAuthorize:

也能判断当前权限

二十、为什么 Filter 必须在 Controller 前执行

如果 Controller 已经执行:

才认证

当然:

没有意义

所以 JWT Filter:

属于请求入口安全链

二十一、addFilterBefore

例如:

.addFilterBefore(
    authenticationTokenFilter,
    UsernamePasswordAuthenticationFilter.class
)

意思:

把 JWT Filter
放在指定 Security Filter 前面

二十二、为什么 Filter 顺序很重要

Security Filter Chain:

有很多过滤器

如果顺序错误:

认证信息还没建立

就可能:

先进入授权判断

导致:

401 / 403 异常

二十三、TokenService 再深入

TokenService 常负责:

获取请求 Token

创建 Token

解析 Token

Redis Key 构造

存 LoginUser

取 LoginUser

删 LoginUser

刷新 TTL

二十四、Token + Redis 关系

客户端:

Bearer Token

服务端:

token
↓
解析 login key / uuid
↓
Redis
↓
LoginUser

二十五、为什么 LoginUser 在 Redis

可以实现:

主动失效

踢人下线

在线用户

续期

多实例共享

二十六、Token 刷新

若依通常不是:

每次请求都无脑延长

而是:

接近过期时间时
才刷新 TTL

这样:

减少 Redis 写操作

二十七、过期相关字段

LoginUser 可能维护:

loginTime

expireTime

TokenService:

据此判断是否需要刷新

二十八、Redis TTL 与 LoginUser.expireTime

两个概念应:

保持一致

否则可能:

Java 对象认为没过期

Redis Key 已过期

或者反过来。

框架:

统一维护

二十九、SecurityUtils

这是若依非常常用的:

安全工具类

常见方法:

getLoginUser()

getUserId()

getUsername()

getAuthentication()

isAdmin()

三十、业务代码获取当前用户

不应该:

前端传 currentUserId

而应该:

Long userId =
        SecurityUtils
            .getUserId();

三十一、为什么

前端:

不可信

用户完全可以:

伪造 userId

三十二、401 是什么

Unauthenticated

常见原因:

没 Token

Token 无效

Token 过期

Redis LoginUser 不存在

三十三、AuthenticationEntryPointImpl

认证失败时:

Spring Security

会进入:

AuthenticationEntryPoint

若依自定义:

AuthenticationEntryPointImpl

三十四、它做什么

通常:

返回 JSON 401

而不是:

跳到传统 HTML 登录页面

因为:

前后端分离

三十五、403 是什么

Authenticated
但没有 Permission

例如:

已经登录

访问:

/system/user/remove

但没有:

system:user:remove

结果:

403

三十六、401 和 403 一定要区分

401
你是谁都没有确认

403
已经知道你是谁
但你不能做

三十七、前端处理

401:

清 Token
回登录页

403:

提示无权限
不要清登录状态

三十八、LogoutSuccessHandlerImpl

若依退出:

不是简单页面跳转

后端会:

解析当前 Token
↓
找到 LoginUser
↓
删除 Redis 登录状态
↓
记录退出相关日志
↓
返回成功

三十九、为什么退出需要后端

如果只:

前端 localStorage.removeItem(token)

Redis:

LoginUser 还在

旧 Token:

理论上仍可能有效

四十、@EnableMethodSecurity

Spring Boot 3 / Spring Security 新配置:

@EnableMethodSecurity(
    prePostEnabled = true,
    securedEnabled = true
)

允许使用:

@PreAuthorize

等方法级权限。


四十一、@PreAuthorize

若依常见:

@PreAuthorize(
    "@ss.hasPermi('system:user:list')"
)

Spring:

方法执行前

先判断。


四十二、@ss

是一个:

Spring Bean 的名称

经典若依中:

PermissionService

注册为:

ss

四十三、PermissionService

它不是:

数据库权限 Service

而是:

运行时 Security 表达式判断服务

四十四、hasPermi

逻辑大意:

获取当前 LoginUser
↓
取 permissions
↓
是否包含指定权限

四十五、hasAnyPermi

类似:

多个权限满足任意一个

四十六、hasRole

判断:

角色编码

四十七、为什么接口更建议 Permission

例如:

ROLE_ADMIN

太粗。

权限:

system:user:export

更精确。


四十八、超级管理员

若依通常设置:

管理员特殊通配

例如:

*:*:*

含义:

所有权限

四十九、前端 permissions 里可能看到

*:*:*

此时:

按钮全部允许

五十、匿名访问

有些接口:

不需要登录

例如:

登录

验证码

公开文章

五十一、SecurityConfig 直接 permitAll

可以:

.requestMatchers(
    "/login",
    "/captchaImage"
)
.permitAll()

五十二、@Anonymous

若依还提供:

匿名访问注解

可以在:

Controller

Method

上标记。


五十三、为什么 @Anonymous 有价值

如果所有公开 URL:

都写进 SecurityConfig

配置会:

越来越长

业务接口:

@Anonymous
@GetMapping("/public/list")

更直观。


五十四、PermitAllUrlProperties

框架会扫描:

@Anonymous

对应的:

RequestMapping 路径

然后:

加入允许匿名访问的 URL

五十五、注意版本差异

有些分支:

匿名 URL 收集逻辑

可能放在:

PermitAllUrlProperties

或其他配置类。

阅读时:

全局搜索 @Anonymous

最好。


五十六、不要滥用 @Anonymous

错误:

接口 401
↓
懒得排错
↓
直接加 @Anonymous

这可能:

造成安全漏洞

五十七、真正 401 应该排查

Token Header

JWT Filter

Redis LoginUser

SecurityConfig

URL 匹配

不是:

直接放行

五十八、数据权限是什么

前面已经说:

功能权限
决定能不能查询

数据权限
决定能查询哪些数据

五十九、若依 DataScope 常见范围

经典若依通常包括:

全部数据

自定义数据

本部门数据

本部门及以下数据

仅本人数据

具体 code:

按当前版本 SysRole 常量为准

六十、例子

总部管理员:

全部用户

辽宁分公司经理:

辽宁分公司及下级部门

普通员工:

仅本人

六十一、角色中的 dataScope

sys_role:

通常有 data_scope 字段

用于:

决定角色数据范围

六十二、自定义数据范围

如果角色:

自定义部门

通过:

sys_role_dept

保存允许的部门。


六十三、@DataScope

若依经典使用:

@DataScope(
    deptAlias = "d",
    userAlias = "u"
)

标在:

Service 方法

六十四、为什么是 Service

数据权限属于:

业务查询逻辑

比放 Controller:

更合理

六十五、DataScopeAspect

它通过:

AOP

拦截:

@DataScope

方法。


六十六、执行流程

Service Method
↓
@DataScope
↓
DataScopeAspect
↓
Current LoginUser
↓
Current Roles
↓
role.dataScope
↓
生成 SQL 条件片段
↓
塞入 params.dataScope
↓
Mapper XML
↓
拼接到查询 SQL

六十七、这和你前面 AOP 完全连接

@DataScope:

自定义注解

DataScopeAspect:

切面

六十八、params.dataScope

若依很多 BaseEntity:

有 params Map

DataScopeAspect 会向里面:

塞数据权限 SQL

六十九、Mapper XML

经典:

${params.dataScope}

把:

权限 SQL

拼到目标查询中。


七十、为什么这里使用 ${}

因为内容:

是一段 SQL 片段

不是:

一个普通参数值

七十一、这是不是 SQL 注入风险

如果 ${} 内容来自:

用户输入

非常危险。

若依这里:

应由框架内部 DataScopeAspect
根据角色和固定规则生成

不能让:

前端直接控制

七十二、非常重要

不要写接口:

{
    "dataScope":
    "or 1=1"
}

然后把它:

塞 params.dataScope

这就是:

严重 SQL 注入

七十三、deptAlias

例如 SQL:

FROM sys_user u
LEFT JOIN sys_dept d
ON u.dept_id = d.dept_id

@DataScope:

deptAlias = "d"

生成的 SQL:

使用 d.dept_id

七十四、userAlias

如果:

仅本人

需要:

u.user_id

七十五、Alias 写错

例如 SQL 用:

su

注解写:

u

可能:

SQL 报未知字段/别名

七十六、DataScope 不是所有查询自动生效

只有:

被 @DataScope 拦截

并且 Mapper:

正确拼入 params.dataScope

才有效。


七十七、这是一个高频安全坑

你新增业务模块:

只写 Controller @PreAuthorize

却忘了:

@DataScope

结果:

普通用户能查整个表

七十八、功能权限和数据权限完整链路

@PreAuthorize
↓
有没有 list 权限
↓
有
↓
@DataScope
↓
可以看哪些数据
↓
Mapper SQL

七十九、DataScope 不能只靠前端筛选

错误:

前端只显示当前部门

后端:

SELECT *

攻击者直接:

调用 API

仍能拿全部数据。


八十、多个角色怎么办

一个用户:

可能有多个 Role

若依需要根据:

多个 dataScope

组合权限。


八十一、组合策略

典型目标:

不要让多个角色叠加后
反而比应有权限更小

或者根据:

权限上下文

决定对应范围。

具体实现:

阅读当前 DataScopeAspect

八十二、为什么数据权限比 RBAC 更难

RBAC 判断:

Set.contains(permission)

很直接。

DataScope:

要改变 SQL 查询范围

涉及:

部门树

多角色

本人

自定义部门

SQL Alias

八十三、部门树

sys_dept:

通常是一棵树

字段可能:

dept_id
parent_id
ancestors

八十四、ancestors

保存:

祖先路径

例如:

0,100,101

便于:

查询下级部门

八十五、本部门及以下

需要:

当前 deptId
+
所有 descendant departments

八十六、为什么组织结构常用树

企业:

集团
↓
分公司
↓
部门
↓
小组

天然:

树形层级

八十七、数据字典是什么

例如数据库:

status = 0

页面不应该显示:

0

而应该:

正常

数据库:

status = 1

显示:

停用

八十八、为什么不用前端 if

错误:

if (status === "0") {
    return "正常"
}
if (status === "1") {
    return "停用"
}

项目里:

几十个页面

就会:

重复很多

八十九、若依数据字典

核心表通常:

sys_dict_type

sys_dict_data

九十、sys_dict_type

字典类型。

例如:

sys_normal_disable

表示:

正常 / 停用

九十一、sys_dict_data

保存具体:

label

value

sort

css class

list class

status

等。


九十二、例子

字典:

sys_normal_disable

数据:

0 → 正常

1 → 停用

九十三、为什么字典类型要稳定

代码中:

useDict('sys_normal_disable')

Excel:

dictType="sys_normal_disable"

如果随意改类型:

前后端都会受影响

九十四、字典后端查询

典型:

根据 dictType
查询有效 dict data

九十五、字典缓存

若依通常会:

缓存字典数据

减少:

每个页面都查数据库

九十六、字典缓存放哪里

常见:

Redis

或框架内的:

Cache

具体:

看当前版本

九十七、DictUtils

经典若依中常见:

字典工具类

用于:

获取字典
转换 label/value

九十八、字典缓存初始化

系统启动或首次查询:

加载字典

然后:

缓存

管理员修改字典后:

刷新 / 删除缓存

九十九、为什么字典修改后页面没变化

常见:

Redis/本地字典缓存还旧

需要:

刷新字典缓存

一百、前端 Vue3 useDict

若依 Vue3 常见:

const {
    sys_normal_disable
} =
    proxy.useDict(
        "sys_normal_disable"
    )

或者根据具体 Vue3 实现:

useDict(...)

返回:

字典选项

一百零一、字典 Select

<el-select
    v-model="form.status"
>
    <el-option
        v-for="dict in sys_normal_disable"
        :key="dict.value"
        :label="dict.label"
        :value="dict.value"
    />
</el-select>

一百零二、字典表格显示

可以通过:

dict-tag

组件。


一百零三、DictTag

例如:

<dict-tag
    :options="sys_normal_disable"
    :value="row.status"
/>

一百零四、为什么不用 el-tag 自己 if

DictTag:

统一转换

统一颜色样式

统一空值处理

一百零五、前后端字典关系

sys_dict_type
↓
sys_dict_data
↓
Backend API / Cache
↓
Vue useDict
↓
DictTag / Select

一百零六、POI 也能使用字典

上一章:

@Excel(
    name = "状态",
    dictType =
        "sys_normal_disable"
)

ExcelUtil:

读取字典

导出:

0 → 正常

一百零七、数据字典串起三层

MySQL
0

↓ Dictionary

Vue
正常

Excel
正常

一百零八、哪些数据适合字典

适合:

状态

性别

业务类型

审核结果

固定分类

一百零九、什么不适合字典

例如:

商品

用户

学校

部门

这些:

是独立业务实体

不应该:

硬塞数据字典

一百一十、字典 vs 枚举

Java Enum:

代码级固定集合

数据字典:

后台可配置集合

一百一十一、什么时候用 Enum

例如:

系统内部绝对固定业务状态

需要:

编译期约束

一百一十二、什么时候用 Dict

例如:

管理员希望改变 label

改变排序

改变 Tag 风格

一百一十三、Enum + Dict 可以共存

后端:

APPROVED("1")

前端:

1 → 已通过

由字典展示。


一百一十四、防重复提交是什么

用户连续:

双击保存

可能发:

两次 POST

导致:

重复订单

重复记录

重复审批

一百一十五、若依 @RepeatSubmit

若依提供:

@RepeatSubmit

加在:

Controller Method

上。


一百一十六、默认思路

例如:

@RepeatSubmit
@PostMapping
public AjaxResult add(
        @RequestBody BizOrder order
) {

    ...
}

短时间:

相同请求重复进入

会被:

拦截

一百一十七、interval

可以:

@RepeatSubmit(
    interval = 3000
)

表示:

3 秒内

判定重复。

官方当前文档也支持:

自定义 interval 和 message

一百一十八、message

@RepeatSubmit(
    interval = 3000,
    message = "请勿重复提交"
)

一百一十九、底层实现

不同版本可能是:

Interceptor

Aspect

总思想:

请求进入
↓
构造重复提交标识
↓
检查缓存/请求数据
↓
短时间重复
→ 拦截

一百二十、Redis 在防重复提交中的作用

可以保存:

request key

TTL:

几秒

第二次:

Key 仍存在

就拒绝。


一百二十一、重复提交 Key 可以包含

例如:

用户标识

URL

Request Body Hash

Header

具体:

看当前若依实现

一百二十二、@RepeatSubmit 是幂等吗

不是完全等价。

防重复提交:

主要阻挡短时间重复请求

真正幂等:

即使同一个请求重复执行
业务结果也保持一致

一百二十三、例如支付回调

不能只:

@RepeatSubmit(5秒)

因为第三方可能:

10 分钟后重试

仍需要:

业务幂等键
+
数据库唯一约束

一百二十四、因此

@RepeatSubmit
是体验/防抖级保护

业务幂等
是更深层保证

一百二十五、XSS 是什么

XSS:

Cross-Site Scripting

攻击者提交:

<script>
    alert(1)
</script>

如果系统:

原样保存并原样输出

浏览器可能:

执行脚本

一百二十六、若依 XSS 防护

若依框架:

提供 XSS 过滤/校验能力

官方说明中也明确列出:

XSS 防范
脚本过滤

一百二十七、XssFilter

经典实现:

Filter 包装 Request

对:

参数
Request Body

做:

HTML 清理 / 转义

一百二十八、为什么不是所有接口都应该粗暴过滤 HTML

富文本编辑器:

需要合法 HTML

如果全部转义:

富文本失效

一百二十九、XSS Exclude

通常可以配置:

排除某些富文本 URL

但排除后:

也不能完全不管安全

应该:

使用 HTML 白名单 sanitizer

一百三十、XSS 的核心不是“删除 script 字符串”

攻击方式:

很多

正确思路:

输入校验
+
安全 HTML 清理
+
输出转义
+
CSP

一百三十一、若依学习阶段

理解:

XssFilter
为什么存在

哪些接口可能排除

为什么富文本要单独处理

就够。


一百三十二、全局异常处理

企业项目不能:

每个 Controller try/catch

若依使用:

GlobalExceptionHandler

一百三十三、@RestControllerAdvice

典型:

@RestControllerAdvice
public class GlobalExceptionHandler {
}

一百三十四、@ExceptionHandler

针对:

不同异常

统一返回:

AjaxResult

一百三十五、常处理异常

AccessDeniedException

MethodArgumentNotValidException

BindException

MissingServletRequestParameterException

HttpRequestMethodNotSupportedException

ServiceException

Exception

具体:

按当前版本

一百三十六、ServiceException

若依业务层:

经常抛

例如:

throw new ServiceException(
    "用户不存在"
);

一百三十七、为什么不用 RuntimeException 随便抛

ServiceException:

表达“这是已知业务错误”

GlobalExceptionHandler:

可以友好返回 message

一百三十八、未知异常

比如:

NullPointerException

应该:

记录详细日志

客户端:

不要返回完整堆栈

一百三十九、为什么不能把 StackTrace 返回前端

可能泄露:

服务器路径

类名

数据库信息

内部实现

一百四十、参数校验异常

例如:

@NotBlank
private String username;

校验失败:

统一处理

返回:

清晰提示

一百四十一、401/403 和 GlobalExceptionHandler

注意:

Security Filter 中发生的认证异常

很多时候:

还没进入 Controller

所以不会完全依赖:

@ControllerAdvice

这也是为什么:

AuthenticationEntryPoint
AccessDeniedHandler

很重要。


一百四十二、Filter 异常 vs Controller 异常

Filter Layer
↓
Security Handlers

Controller/Service Layer
↓
GlobalExceptionHandler

一百四十三、操作日志 @Log

若依:

@Log(
    title = "用户管理",
    businessType =
        BusinessType.INSERT
)

一百四十四、BusinessType

例如:

OTHER

INSERT

UPDATE

DELETE

GRANT

EXPORT

IMPORT

FORCE

GENCODE

CLEAN

具体:

当前枚举为准

一百四十五、LogAspect

通过:

AOP

拦截:

@Log

方法。


一百四十六、记录什么

常见:

模块

业务类型

请求方法

请求 URL

操作人

IP

参数

返回结果

状态

错误信息

耗时

一百四十七、为什么日志不能无脑保存所有参数

请求里可能有:

password

token

secret

身份证

手机号

日志:

也需要脱敏

一百四十八、若依日志注解可配置

例如:

是否保存请求参数

是否保存响应结果

排除字段

具体:

当前 @Log 定义

一百四十九、操作日志通常异步入库

为什么:

不要让业务请求
等待日志数据库写入

可以:

异步任务

保存。


一百五十、异步日志问题

如果:

应用刚好崩溃

少量日志:

可能没来得及落库

关键审计:

需要更严格设计

一百五十一、登录日志

登录行为:

成功

失败

退出

通常:

单独 sys_logininfor

记录。


一百五十二、操作日志 vs 登录日志

操作日志:

用户进入系统后做了什么

登录日志:

认证过程发生了什么

一百五十三、数据脱敏

官方若依当前也提供:

数据脱敏相关能力

例如:

手机号

身份证

邮箱

银行卡

一百五十四、为什么脱敏应该在输出层

数据库:

仍保存真实必要数据

返回某些场景:

隐藏中间部分

一百五十五、例子

手机号:

138****0000

一百五十六、不要在数据库直接保存脱敏文本代替原数据

除非业务:

根本不需要真实值

否则:

以后无法正常业务处理

一百五十七、若依 RedisCache

若依会封装:

RedisCache

你会看到:

setCacheObject

getCacheObject

deleteObject

setCacheList

getCacheList

等方法。


一百五十八、为什么不直接到处 RedisTemplate

框架:

统一常用操作

业务:

写起来更简单

一百五十九、但是要知道底层仍是 RedisTemplate

不要认为:

RedisCache 是一种新数据库

它只是:

若依工具封装

一百六十、CacheConstants

通常统一定义:

Redis Key Prefix

例如:

LOGIN_TOKEN_KEY

CAPTCHA_CODE_KEY

SYS_DICT_KEY

具体名称:

当前版本为准

一百六十一、为什么常量集中管理

防止:

Key 拼错

不同模块前缀不一致

改 Key 要改几十处

一百六十二、验证码缓存

登录获取验证码:

生成 UUID
↓
生成验证码
↓
Redis:
captcha_codes:{uuid}
↓
TTL
↓
前端拿 uuid + 图片

一百六十三、登录提交

username

password

code

uuid

后端:

Redis 查验证码
↓
比较
↓
立即删除

一百六十四、为什么立即删除验证码

防止:

重复使用

一百六十五、验证码错误

应该:

记录登录失败信息

但不能:

把系统内部异常直接给用户

一百六十六、前端数据字典缓存

Vue 端也可能:

把已加载字典缓存在 Store

避免:

同类型字典重复请求

一百六十七、Pinia 字典 Store

不同 RuoYi-Vue3 版本:

实现细节不同

阅读:

store/modules/dict

或类似模块。


一百六十八、useDict 核心思想

页面声明需要哪几个 dictType
↓
查 Store
├─ 有 → 直接使用
└─ 无
   ↓
   API
   ↓
   Backend
   ↓
   Redis/DB
   ↓
   Store
   ↓
   页面

一百六十九、为什么字典有多级缓存

数据库
↓
后端 Redis
↓
前端 Pinia

目的:

减少重复请求

一百七十、但多级缓存的代价

管理员改字典:

要考虑失效

否则:

后端新
前端旧

一百七十一、字典刷新策略

常见:

管理员修改成功
↓
清后端字典缓存
↓
下一次前端重新获取

如果前端 Store 已缓存:

可能需要刷新页面

或主动:

清对应 Store 字典

一百七十二、字典不是业务主数据

不要用字典管理:

10 万个商品分类节点

字典适合:

小规模枚举型配置

一百七十三、重复提交底层进一步理解

假设接口:

POST /order

用户双击。

请求 A:

12:00:00.001

请求 B:

12:00:00.050

一百七十四、拦截器

A:

生成 Request Signature
↓
写 Redis
TTL 5s
↓
通过

B:

生成同一 Signature
↓
Redis 已存在
↓
拒绝

一百七十五、签名不要只用 URL

如果:

POST /order

用户连续提交:

两个完全不同订单

只看 URL:

可能误判

一百七十六、因此可能加入

Request Body

User Token

URL

等信息。


一百七十七、但 Request Body 太大怎么办

需要:

做 Hash

不要直接把:

几 MB JSON

当 Redis Key。


一百七十八、重复提交缓存 TTL 很短

通常:

几秒

不是:

永久

一百七十九、业务唯一约束仍然需要

例如订单:

UNIQUE(request_no)

这样即使:

RepeatSubmit 失效

数据库仍:

阻止重复业务

一百八十、XSS 与 JSON

很多人以为:

只有表单参数会 XSS

实际上:

JSON Body

同样可能:

携带恶意 HTML

一百八十一、真正安全还需要前端输出规则

Vue 默认:

插值 {{ }}

会做:

文本转义

但:

v-html

会:

渲染 HTML

所以:

v-html 特别谨慎

一百八十二、富文本

如果必须:

v-html

应确保:

后端/前端进行可信 HTML Sanitization

一百八十三、Security Filter 排查技巧

如果接口:

明明应该匿名
却 401

全局搜索:

requestMatchers

permitAll

@Anonymous

PermitAllUrlProperties

一百八十四、如果登录后访问接口仍 401

打断点:

JwtAuthenticationTokenFilter

看:

Header 有没有 Token

getLoginUser 是否返回 null

Redis Key 是什么

一百八十五、IDEA 断点位置

推荐:

JwtAuthenticationTokenFilter.doFilterInternal

TokenService.getLoginUser

PermissionService.hasPermi

DataScopeAspect.doBefore / handleDataScope

GlobalExceptionHandler

一百八十六、为什么这些断点值钱

因为它们正好对应:

认证
授权
数据范围
异常

四条主线。


一百八十七、401 调试流程

Network
↓
Authorization Header
↓
JwtAuthenticationTokenFilter
↓
Token Parse
↓
Redis Key
↓
LoginUser
↓
SecurityContext

一百八十八、403 调试流程

Current LoginUser
↓
permissions
↓
@PreAuthorize expression
↓
PermissionService.hasPermi
↓
Permission String

一百八十九、DataScope 调试流程

Service 是否有 @DataScope
↓
AOP 是否进入
↓
Current Roles
↓
DataScope SQL
↓
params.dataScope
↓
Mapper XML
↓
最终 SQL

一百九十、字典不显示调试

dictType 是否正确
↓
sys_dict_type 是否存在
↓
sys_dict_data 是否有有效数据
↓
后端缓存
↓
字典 API
↓
前端 useDict
↓
DictTag options

一百九十一、RepeatSubmit 不生效

检查:

Controller 是否加注解

拦截器/AOP 是否注册

URL 是否被匹配

Redis 是否可用

请求是否真的相同

一百九十二、XSS 不生效

检查:

XSS 是否开启

URL 是否在 exclusions

Request Content-Type

XssFilter 是否注册

一百九十三、Spring Boot 3 Filter 注册问题

升级版本时:

Servlet API
javax → jakarta

Filter:

可能出现兼容问题

所以不要:

手动复制旧 SpringBoot2 XssFilter

到新分支。


一百九十四、若依为什么维护独立 springboot3 分支

就是为了:

处理 Spring Boot / Security / Jakarta
版本迁移差异

所以:

不要自己把 springboot2
pom 版本号改成 3.x
就认为升级完成

一百九十五、Spring Security 版本混乱常见表现

antMatchers 不存在

WebSecurityConfigurerAdapter 不存在

HttpSecurity Bean 问题

Filter 类型不匹配

javax/jakarta 冲突

一百九十六、正确做法

选择:

官方对应分支

而不是:

手工把核心依赖乱升级

一百九十七、Maven 排错

mvn dependency:tree

看:

spring-security-*

spring-web

jakarta.servlet

javax.servlet

是否:

混版本

一百九十八、框架核心修改原则

若依核心:

SecurityConfig

TokenService

JwtFilter

PermissionService

DataScopeAspect

尽量:

保持稳定

一百九十九、业务扩展应该放哪里

例如学生业务:

StudentController

StudentService

StudentMapper

使用:

若依已有 Security / Redis / DataScope

二百、错误 AI 提示

帮我给若依增加 JWT

若依已经:

有 Token/JWT

AI 很可能:

创建第二套

二百零一、正确提示

当前项目是官方 RuoYi-Vue springboot3。

请先阅读:
SecurityConfig
JwtAuthenticationTokenFilter
TokenService
LoginUser
PermissionService

目标:
给新的 business/student 模块接入现有认证权限体系。

禁止:
新增第二套 JWT Filter
新增第二套 SecurityConfig
新增自定义 LoginInterceptor

二百零二、DataScope AI 提示

请分析当前项目现有 DataScopeAspect 和 @DataScope 使用方式。

为 biz_student 列表增加部门数据权限。

要求:
复用现有 @DataScope,
不要在 Controller 手写 deptId 过滤,
不要接受前端传入 dataScope SQL。

二百零三、字典 AI 提示

请使用若依现有数据字典体系实现学生状态。

dictType:
biz_student_status

要求:
后端数据库保存 code,
前端通过现有 useDict + dict-tag 显示 label,
不要在 Vue 中写 status === '0' 的 if/else。

二百零四、防重复提交 AI 提示

请复用若依现有 @RepeatSubmit,
为“学生批量提交”接口增加 3 秒防重复提交。

不要重新创建 RedisLockUtil。

二百零五、为什么必须明确“复用现有”

AI 默认目标:

把问题解决

不一定知道:

项目已经有成熟实现

企业代码更重要的是:

保持架构一致

二百零六、SecurityConfig 不要每个业务加 URL

普通业务接口:

默认 authenticated

权限:

通过 @PreAuthorize

即可。


二百零七、什么时候 SecurityConfig permitAll

真正:

公开接口

例如:

登录

验证码

公开首页内容

二百零八、不要把业务权限写在 URL 配置里

例如:

/system/user/**
hasRole("admin")

同时 Controller:

又有 @PreAuthorize

容易:

两套规则冲突

若依主要:

方法级 Permission

更清晰。


二百零九、数据权限 Service 示例

@DataScope(
    deptAlias = "d",
    userAlias = "u"
)
public List<BizStudent>
    selectStudentList(
        BizStudent student
    ) {

    return studentMapper
            .selectStudentList(
                student
            );
}

二百一十、Mapper 需要包含

${params.dataScope}

二百一十一、如果 Entity 没继承 BaseEntity

可能:

没有 params

DataScope:

无法按经典方式注入

二百一十二、所以代码生成业务实体

常常:

extends BaseEntity

不只是为了:

createBy

也方便:

params

等框架能力。


二百一十三、DataScope 和分页顺序

典型:

Controller startPage
↓
Service @DataScope
↓
Mapper Query

PageHelper:

负责 LIMIT

DataScope:

负责 WHERE 范围

二百一十四、二者不冲突

最终 SQL:

SELECT ...
FROM ...
WHERE ...
AND data_scope_condition
LIMIT ...

二百一十五、导出也要 DataScope

Controller Export:

不要调用没有 DataScope 的全量查询

应该:

复用列表查询 Service

二百一十六、统计接口也要 DataScope

例如:

GET /student/statistics

如果普通用户只看本部门:

统计也只能统计本部门

二百一十七、这是最容易漏的地方

大家只给:

list()

加 DataScope。

但:

count
summary
export
chart

可能:

全部泄露数据

二百一十八、数据权限检查清单

新增业务模块时:

列表

详情

导出

统计

搜索

批量操作

都问:

当前用户能看到全部吗?

二百一十九、详情接口不能只靠 DataScope list

例如:

GET /student/100

如果 Mapper:

selectById(100)

没有数据权限:

用户可遍历 ID

二百二十、这是水平越权

解决:

详情查询也必须检查资源归属

方式:

带 DataScope 条件查询

或 Service 手工校验所属部门

二百二十一、功能权限解决不了水平越权

用户拥有:

business:student:query

只说明:

能看学生详情功能

不说明:

能看任意学生

二百二十二、这和前面 RBAC 安全知识完全一致

Vertical Authorization
功能权限

Horizontal Authorization
数据对象权限

二百二十三、字典管理页面

管理员可以:

新增字典类型

新增字典数据

启停字典项

排序

设置标签样式

二百二十四、为什么前端 Tag 能有不同颜色

字典数据:

可能包含 listClass / cssClass

DictTag:

根据配置渲染

二百二十五、不要把颜色写死业务页面

如果框架已支持:

字典 listClass

就:

复用

二百二十六、字典删除注意

某 dictType:

正在被代码使用

删除后:

前端 label 无法转换

所以:

字典类型也是一种配置契约

二百二十七、字典 Value 不要频繁修改

例如:

0 = 正常
1 = 停用

数据库已有:

百万条 status

你把:

0 改成 A

旧数据:

全失配

二百二十八、一般只改 Label 更安全

0
保持

正常
改成
启用

数据库:

无需迁移

二百二十九、数据字典与数据库约束

字典:

是展示/配置体系

它本身不一定:

自动形成数据库 CHECK

所以后端:

仍应校验提交值

二百三十、不能信任 Select

即使前端:

下拉只有 0/1

用户也可以 Postman:

status=999

后端:

应该拒绝非法 code

二百三十一、操作日志和敏感参数

例如登录接口:

password

不能:

完整记日志

二百三十二、Token 同样不能完整记录

如果日志泄露:

Token

攻击者可能:

直接冒用登录

二百三十三、日志的三个目标

排错

审计

统计

不是:

把所有数据全保存

二百三十四、日志清理

sys_oper_log:

长期会变大

需要:

分页

清理策略

归档

二百三十五、异常日志

未知异常:

完整 StackTrace

应该:

服务器日志记录

而不是:

数据库操作日志一定全部存

二百三十六、防重复提交与 Redis 故障

如果 RepeatSubmit 依赖 Redis:

Redis 挂了

应该:

明确策略

二百三十七、不能为了“可用”无脑跳过

如果接口:

重复提交风险很高

可以:

宁可失败

而不是:

放过所有重复订单

二百三十八、普通表单

Redis 防重异常:

也可以结合数据库唯一约束

作为兜底。


二百三十九、SecurityContext 为什么通常是线程级

传统 Servlet:

一个请求
由一个工作线程处理

SecurityContextHolder:

通常通过 ThreadLocal
关联当前认证

二百四十、异步线程注意

如果你:

@Async

新线程里直接:

SecurityUtils.getUserId()

不一定:

自动有原上下文

二百四十一、因此异步任务

最好:

显式传 userId

或使用:

正确的 Security Context 传播机制

二百四十二、不要在线程池里长期依赖 ThreadLocal 用户

否则:

上下文丢失 / 污染

二百四十三、Token 续期并发

一个页面:

并发 10 个 API

如果都发现:

快过期

可能:

都刷新 Redis TTL

一般:

影响不大

但高并发:

可以优化

二百四十四、为什么若依框架代码值得读

因为它把:

Security
Redis
AOP
MyBatis
Vue

真正:

组合成工程实现

二百四十五、不要只复制类

重点问:

为什么这个类存在?

它位于哪个调用阶段?

如果删掉会怎样?

谁调用它?

它依赖谁?

二百四十六、Cursor 阅读 Security 提示

请只分析当前 RuoYi 项目 Security 请求链,不修改文件。

从一个需要登录的 GET /system/user/list 请求开始,
列出请求依次经过的真实 Filter、SecurityConfig、
TokenService、SecurityContext、@PreAuthorize、PermissionService。

必须给出真实文件路径、类名和方法名。
如果当前分支与经典若依类名不同,以实际代码为准。

二百四十七、Cursor 阅读 DataScope 提示

请只分析当前项目的数据权限。

从 SysUserService 的列表查询开始,
追踪 @DataScope → DataScopeAspect → params.dataScope → Mapper XML。

说明:
1. 每种 dataScope 如何生成 SQL
2. deptAlias/userAlias 的作用
3. 多角色如何组合
4. 哪些查询可能绕过数据权限

二百四十八、Cursor 阅读字典提示

请从前端 useDict('sys_normal_disable') 开始,
追踪到:
前端 Store
→ 字典 API
→ 后端 Controller
→ Service
→ Redis Cache
→ sys_dict_data。

必须列出真实文件路径。

二百四十九、Cursor 改代码前 Git

git status

确保:

当前改动清楚

然后:

git add .
git commit -m "chore: checkpoint before framework analysis"

二百五十、为什么框架代码更要 checkpoint

如果 AI:

误改 SecurityConfig

可能整个项目:

都登录不了

Git:

立即恢复

二百五十一、练习 1:画 Security 请求链

要求:

Browser
↓
JwtFilter
↓
Redis
↓
SecurityContext
↓
Controller
↓
@PreAuthorize

自己不看笔记:

画出来

二百五十二、练习 2:401

删除前端 Token。

访问:

/system/user/list

观察:

401

断点:

JwtAuthenticationTokenFilter

二百五十三、练习 3:Redis 登录状态

正常登录。

Redis 查看:

login token key

然后:

手工删除

前端再次请求。

观察:

401

二百五十四、练习 4:403

创建角色:

只有 user:list

不给:

user:remove

直接调用删除 API。

确认:

403

二百五十五、练习 5:@Anonymous

创建:

GET /demo/public

先:

不加 @Anonymous

无 Token:

401

再:

按当前若依匿名方式配置

确认:

200

二百五十六、练习 6:DataScope

建立:

总部

A 部门

B 部门

分别创建用户。


二百五十七、练习 7

角色:

本部门数据

登录 A 部门用户。

查询:

只看到 A 部门

二百五十八、练习 8

角色:

本部门及以下

创建:

A
└─ A1

确认:

能看到 A + A1

二百五十九、练习 9

故意:

删掉 Service @DataScope

测试:

是否出现越权

然后恢复。

这个练习:

只在本地测试环境

二百六十、练习 10:字典

新增:

biz_student_status

数据:

0=在读
1=休学
2=毕业

二百六十一、练习 11

Vue:

useDict
+
el-select

实现:

学生状态选择

二百六十二、练习 12

列表:

dict-tag

把:

0/1/2

显示:

在读/休学/毕业

二百六十三、练习 13

Excel:

@Excel(
    name = "状态",
    dictType =
        "biz_student_status"
)

导出:

看 Label

二百六十四、练习 14:RepeatSubmit

接口:

POST /student

增加:

@RepeatSubmit(
    interval = 3000
)

快速双击。

观察:

第二次被拒

二百六十五、练习 15:数据库唯一约束

student_no:

UNIQUE

即使:

3 秒后再次提交同学号

数据库:

仍阻止

理解:

防重复提交 ≠ 业务唯一

二百六十六、练习 16:XSS

本地测试:

提交简单 HTML 字符串

观察:

框架如何处理

不要:

对公开生产系统做安全测试

二百六十七、练习 17:GlobalExceptionHandler

Service:

throw new ServiceException(
    "测试业务异常"
);

观察:

统一 JSON

二百六十八、练习 18:参数校验

DTO:

@NotBlank
private String studentName;

提交空字符串。

观察:

统一错误返回

二百六十九、练习 19:@Log

新增学生:

@Log(
    title = "学生管理",
    businessType =
        BusinessType.INSERT
)

执行后:

操作日志页面查看

二百七十、练习 20:权限 + DataScope + Log

一个接口同时:

@PreAuthorize
@Log

Service:

@DataScope

理解调用顺序和不同职责。


二百七十一、必须掌握 Security

SecurityFilterChain

JwtAuthenticationTokenFilter

TokenService

SecurityContextHolder

Authentication

LoginUser

PermissionService

401

403

二百七十二、必须掌握权限

@PreAuthorize

hasPermi

hasRole

*:*:*

@Anonymous

二百七十三、必须掌握数据权限

@DataScope

DataScopeAspect

deptAlias

userAlias

params.dataScope

sys_role.data_scope

sys_role_dept

二百七十四、必须掌握字典

sys_dict_type

sys_dict_data

dictType

DictUtils

useDict

dict-tag

Redis Dict Cache

二百七十五、必须掌握框架公共能力

@RepeatSubmit

XssFilter

GlobalExceptionHandler

ServiceException

@Log

LogAspect

RedisCache

CacheConstants

二百七十六、面试题 1:若依 Security 请求链

答:

请求先进入 Spring Security Filter Chain。

JwtAuthenticationTokenFilter 从 Header 获取 Token,
通过 TokenService 从 Redis 恢复 LoginUser,
再创建 Authentication 放入 SecurityContext。

请求进入 Controller 前,
@PreAuthorize 通过 PermissionService
读取当前 LoginUser 权限集合完成授权判断。

二百七十七、面试题 2:SecurityContext 是什么

答:

SecurityContext 保存当前安全上下文,
其中最核心的是 Authentication。

若依认证成功后会把 LoginUser
包装进 Authentication,
后续 Controller 和权限判断就能识别当前用户。

二百七十八、面试题 3:401 与 403

答:

401 表示未完成有效认证,
例如没有 Token、Token 失效或 Redis LoginUser 不存在。

403 表示已经成功认证,
但当前用户没有访问目标资源所需权限。

二百七十九、面试题 4:为什么若依 Token 还用 Redis

答:

Redis 保存 LoginUser,
可以实现在线用户、主动注销、踢人下线、
Token 续期以及多实例共享登录状态。

因此若依并不是单纯完全无状态的 JWT 使用方式。

二百八十、面试题 5:@Anonymous 怎么工作

答:

若依可以通过 @Anonymous 标记允许未登录访问的 Controller 或方法。

框架会收集这些接口路径,
加入 Spring Security 的 permitAll 配置,
从而不要求登录认证。

二百八十一、面试题 6:@DataScope 原理

答:

@DataScope 是数据权限注解。

DataScopeAspect 在 Service 查询前读取当前用户角色的数据范围,
生成对应的部门/用户 SQL 条件,
写入查询对象的 params.dataScope。

Mapper XML 再把这个由框架生成的 SQL 片段
拼入最终查询,从而限制返回数据范围。

二百八十二、面试题 7:为什么 ${params.dataScope} 没直接 SQL 注入

答:

${} 本身确实是字符串直接拼接,
所以不能接受前端可控内容。

若依的 dataScope SQL 应由框架内部
根据当前用户角色和固定规则生成,
用户不能直接传入任意 SQL。

如果把用户输入塞进 dataScope,
就会形成严重 SQL 注入风险。

二百八十三、面试题 8:数据权限和功能权限

答:

功能权限通过 permission code
判断用户能不能执行某个功能。

数据权限进一步决定已经拥有功能后,
允许查询哪些业务记录。

例如两个人都有 user:list,
但一个能看全公司,
另一个只能看自己部门。

二百八十四、面试题 9:若依数据字典

答:

若依通过 sys_dict_type 和 sys_dict_data
集中维护枚举型配置。

数据库保存稳定 code,
前端通过 useDict 和 dict-tag
把 code 转换为 label,
Excel 也可以通过 dictType 复用同一字典。

二百八十五、面试题 10:字典和枚举区别

答:

Java Enum 更适合代码层稳定、需要编译期约束的固定集合。

数据字典更适合希望后台动态调整 Label、排序、
状态和展示样式的配置项。

实际项目中二者可以配合使用。

二百八十六、面试题 11:@RepeatSubmit 是幂等吗

答:

它主要防止用户在很短时间内重复发送相同请求,
属于防重复提交机制。

完整业务幂等还需要业务唯一键、
数据库唯一约束、幂等 Token 等更长期的保证。

二百八十七、面试题 12:若依全局异常如何处理

答:

Controller/Service 层异常通常由
@RestControllerAdvice + @ExceptionHandler
统一转换为 AjaxResult。

Security Filter 层的未认证和拒绝访问异常
则更多通过 AuthenticationEntryPoint、
AccessDeniedHandler 等 Security 机制处理。

二百八十八、面试题 13:@Log 原理

答:

@Log 是若依的操作日志注解。

LogAspect 使用 Spring AOP 拦截带 @Log 的方法,
读取模块、业务类型、请求信息、操作用户、参数和结果,
再记录系统操作日志。

二百八十九、面试题 14:为什么不能记录所有请求参数

答:

请求中可能包含密码、Token、Secret、身份证等敏感信息。

如果日志无脑记录全部内容,
日志系统本身会变成敏感数据泄露源。

因此应排除或脱敏敏感字段。

二百九十、面试题 15:为什么直接把 SpringBoot2 若依升级到 3 容易失败

答:

Spring Boot 3 同时涉及 Spring Security API 变化
和 javax 到 jakarta 的生态迁移。

旧代码中的 WebSecurityConfigurerAdapter、
antMatchers、Servlet 类型和部分 Filter 配置
都可能不兼容。

更稳的方式是使用若依官方对应 springboot3 分支,
而不是只修改 pom 中 SpringBoot 版本号。

二百九十一、若依框架核心知识树

RuoYi Framework
│
├─ Security
│  ├─ SecurityFilterChain
│  ├─ JwtAuthenticationTokenFilter
│  ├─ TokenService
│  ├─ LoginUser
│  ├─ SecurityContext
│  ├─ PermissionService
│  ├─ 401
│  └─ 403
│
├─ Authorization
│  ├─ @PreAuthorize
│  ├─ hasPermi
│  ├─ hasRole
│  └─ @Anonymous
│
├─ Data Scope
│  ├─ @DataScope
│  ├─ DataScopeAspect
│  ├─ sys_role
│  ├─ sys_role_dept
│  ├─ deptAlias
│  └─ params.dataScope
│
├─ Dictionary
│  ├─ sys_dict_type
│  ├─ sys_dict_data
│  ├─ DictUtils
│  ├─ Redis Cache
│  ├─ useDict
│  └─ DictTag
│
├─ Protection
│  ├─ @RepeatSubmit
│  ├─ XSS
│  └─ Validation
│
├─ Exception
│  ├─ ServiceException
│  └─ GlobalExceptionHandler
│
├─ Logging
│  ├─ @Log
│  ├─ LogAspect
│  ├─ OperLog
│  └─ LoginInfo
│
└─ Redis
   ├─ RedisCache
   ├─ LoginUser
   ├─ Captcha
   └─ Dict Cache

二百九十二、Security 完整流程图

Vue
↓
Authorization: Bearer token
↓
SecurityFilterChain
↓
JwtAuthenticationTokenFilter
↓
TokenService
↓
Redis
↓
LoginUser
↓
Authentication
↓
SecurityContextHolder
↓
Controller
↓
@PreAuthorize
↓
PermissionService
↓
Allow / 403

二百九十三、DataScope 完整流程图

Controller
↓
startPage()
↓
Service
↓
@DataScope
↓
DataScopeAspect
↓
SecurityContext
↓
LoginUser.roles
↓
Role.dataScope
↓
生成 SQL
↓
params.dataScope
↓
Mapper XML
↓
MySQL
↓
只返回允许的数据

二百九十四、字典完整流程图

sys_dict_type
+
sys_dict_data
↓
Backend Service
↓
Redis Cache
↓
Dictionary API
↓
Vue useDict
↓
Pinia/Cache
↓
el-select / dict-tag

二百九十五、RepeatSubmit 流程图

POST Request
↓
@RepeatSubmit
↓
Interceptor / Aspect
↓
User + URL + Request Signature
↓
Redis
├─ 不存在
│  ↓
│ SET TTL
│  ↓
│ Controller
│
└─ 已存在
   ↓
   Reject

二百九十六、异常处理层次图

Security Filter Error
↓
AuthenticationEntryPoint / AccessDeniedHandler

Controller / Service Error
↓
GlobalExceptionHandler
↓
AjaxResult

二百九十七、本章最重要的安全检查

新增若依业务模块时,检查:

1. Controller 是否有 @PreAuthorize

2. Service 列表是否需要 @DataScope

3. 详情是否存在水平越权

4. Export 是否复用 DataScope

5. Statistics 是否复用 DataScope

6. 前端 v-hasPermi 是否和后端一致

7. Dict value 是否后端校验

8. POST 是否需要防重复/幂等

9. 用户输入是否存在 XSS 风险

10. @Log 是否会记录密码/Token

二百九十八、使用 AI/Cursor 最重要的原则

不要:

让 AI “重新设计若依架构”

先:

让 AI 阅读真实源码

然后:

复用现有能力

提示词必须包含:

真实技术栈

若依分支

目标业务

要复用的现有类

禁止修改的核心模块

需要保持的权限规范

二百九十九、完整 Cursor 模板

当前项目基于官方 RuoYi-Vue springboot3 + RuoYi-Vue3。

请先阅读项目真实源码,不要修改。

重点阅读:
SecurityConfig
JwtAuthenticationTokenFilter
TokenService
LoginUser
PermissionService
DataScopeAspect
RedisCache
GlobalExceptionHandler

任务:
分析新增业务模块 biz_student
应该如何接入:
1. 登录认证
2. @PreAuthorize
3. @DataScope
4. 数据字典
5. @RepeatSubmit
6. @Log

要求:
必须列出真实文件路径和现有方法。
禁止新增第二套 JWT、第二套 SecurityConfig、
自定义登录拦截器或另一套 Redis Token 体系。

三百、本章最终总结

若依 Security 核心链:

Token
↓
JWT Filter
↓
Redis LoginUser
↓
SecurityContext
↓
@PreAuthorize
↓
PermissionService

401:

未认证

403:

已认证但无权限

数据权限:

@DataScope
↓
DataScopeAspect
↓
params.dataScope
↓
Mapper SQL

数据字典:

sys_dict_type
+
sys_dict_data
↓
Cache
↓
useDict
↓
DictTag

防重复:

@RepeatSubmit

但要记:

防重复提交
≠
完整业务幂等

安全:

XSS Filter
+
输入校验
+
输出转义

异常:

GlobalExceptionHandler

日志:

@Log
+
LogAspect

这一章最关键的是:

若依不是一堆工具类,
而是一套已经组织好的企业框架能力。

真正开发时应该:

复用 Security

复用 Token

复用 Redis

复用 Permission

复用 DataScope

复用 Dict

复用 Exception

复用 Log

不要:

每做一个业务模块
又自己重新造一套框架。

到这里:

第三阶段课程表中的
两个“若依脚手架”单元
已经学习完成。

按照课程表,下一阶段正式进入:

淘车湾项目实战

第一篇:

《淘车湾项目实战(一):项目分析、数据库设计与若依脚手架改造》

会开始把前面所有知识:

SpringBoot
Vue3
MyBatis
Redis
Git
Activiti
若依
RBAC

真正组合成:

企业项目

官方参考

若依官方文档:

https://doc.ruoyi.vip/ruoyi-vue/

若依后台手册当前仍明确包含:

异常处理
数据脱敏
系统日志
数据权限
防重复提交

等框架能力。

官方项目介绍也明确指出 RuoYi-Vue:

基于 SpringBoot、Spring Security、JWT、Vue

并内置:

菜单/按钮授权
数据权限
日志
代码生成

等后台功能。

版本提醒:

若依长期更新。

Spring Boot 2 / 3 / 4
以及 Vue2 / Vue3
的具体 Security API 和文件实现会变化。

学习时掌握:
请求链
认证
授权
数据范围
缓存
字典
异常
日志

真正开发时再以当前分支源码为准。