拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

若依RuoYi-Vue匿名访问核心:PermitAllUrlProperties源码解析

若依RuoYi-Vue匿名访问核心:PermitAllUrlProperties源码解析 做 Java 后端开发的多多少少都会跟 RuoYi 这套框架打交道尤其是 RuoYi-Vue 前后端分离版本。如果你在它上面做过二次开发大概率会在某个深夜对着“明明放行了为什么还是 401”这种问题挠头。这时候你需要认识一下PermitAllUrlProperties。这个类负责一件听起来很小、但实际很关键的事在应用启动时扫描所有 Controller 里标注了Anonymous注解的接口把这些接口的 URL 收集成一张“匿名白名单”。后续请求进来时只要命中这张名单就不需要解析 token、不需要登录态直接放行。换句话说它决定了哪些接口可以“不穿衣服”跑在路上是若依匿名访问机制的核心入口。这篇文章我会从若依的鉴权链路讲起拆解PermitAllUrlProperties的源码、运行时机、实际用法再把我踩过的坑和排查思路一并整理出来适合正在做若依二开、或者想彻底理解若依接口鉴权机制的开发同学。1. 先从若依的鉴权链路说起这个类到底在解决什么问题1.1 登录、Token 与动态权限校验的基本流程RuoYi-Vue 的鉴权链路可以拆成三步登录拿 token、请求带 token、后端验 token。用户在登录接口输入用户名密码后端校验通过后生成一个随机 token把LoginUser对象和用户权限信息缓存到 Redis 里token 本身返回给前端。前端每次请求在请求头里带上Authorization: Bearer token。后端收到请求后TokenFilter会从请求头解析这个 token再拿着 token 去 Redis 里换取LoginUser。换到了就把用户的登录状态放进SecurityContextHolder后续方法级权限注解PreAuthorize(ss.hasPermi(system:user:list))才能从当前上下文里拿到用户信息判断这个用户有没有某个菜单或按钮权限。如果换不到那就看这个接口是不是匿名接口如果是就走放行逻辑不是就直接返回 401。所以这里隐含了一个问题像/login、/captchaImage、/register这类接口用户本来就没登录不可能带 token后端必须把它们当成“匿名可访问”的接口单独放行。若依的做法就是用Anonymous注解标记这些接口再用PermitAllUrlProperties统一收集。1.2 为什么不在 SecurityConfig 里写死而要引入 Anonymous 扫描机制很多刚接触若依的同学会问Spring Security 不是有permitAll()吗在SecurityConfig里把这些路径一个个配进去不就行了确实可以传统单体版若依就是这么干的在SecurityConfig的authorizeRequests()里把/login、/captchaImage等地址permitAll()。但这种方式在前后端分离的 RuoYi-Vue 上有一个明显的痛点接口数量一多集中式配置会变得很难维护。想象一下这个场景你负责的一个后台管理系统有 200 个接口其中 15 个是公开的。如果全部集中在SecurityConfig里写死每次新增公开接口都要去改这个类改着改着就漏了而且后来的人根本不知道某个接口是不是故意放行的。更麻烦的是不同业务模块的开发者改同一个安全配置文件冲突是迟早的事。Anonymous这种设计把“是否匿名”的决策权下放到了接口方法本身属于声明式编程思路。开发者想开放哪个接口直接在方法上标一个注解剩下的交给框架去扫描收集。新增接口时不需要碰全局安全配置改错了也只影响单个接口风险范围小得多。两种方式的差别用大白话讲就是集中式写死路径像是学校门口门卫手里的一沓纸质名单每次有新人进来就要改名单Anonymous像是给每间允许自由进出的教室门上贴一个“无需刷卡”的标志教室变了门卫照常巡逻就行了。1.3 传统若依和若依-Vue 在这个机制上的差异很多网上的教程混着讲导致初学者容易懵。这里把两个版本的区别说清楚。传统单体版 RuoYi就是那个用 Thymeleaf 做页面的版本使用的是直接配置 Spring Security 的方式它的匿名 URL 配置在SecurityConfig里写死类名不一定叫PermitAllUrlProperties。而 RuoYi-Vue 前后端分离版因为引入了更细粒度的接口权限模型才在ruoyi-framework模块下单独提供了PermitAllUrlProperties这个配置类配合Anonymous注解来管理匿名 URL。所以你如果是在 RuoYi-Vue 或它的衍生版本上做开发才会遇到PermitAllUrlProperties。如果你用的是传统单体版搜Anonymous可能压根搜不到就只能去SecurityConfig里找permitAll()的配置了。搞清这个前提看代码时才不会张冠李戴。2. PermitAllUrlProperties 源码拆解扫描、收集、匹配三步曲2.1 类的骨架InitializingBean 和 ApplicationStartedEvent 的作用PermitAllUrlProperties在若依-Vue 中位于com.ruoyi.framework.config包下完整实现大致是实现InitializingBean接口同时监听ApplicationStartedEvent事件。它有两个核心时间点。第一个是afterPropertiesSet()这是InitializingBean接口的回调方法Spring 在完成 Bean 属性注入后会执行它。为什么若依不直接用PostConstruct因为这里需要确保RequestMappingHandlerMapping已经初始化完毕能拿到容器里所有接口映射afterPropertiesSet在依赖注入完成后才调用正是干这件事的合适时机。第二个是onApplicationEvent(ApplicationStartedEvent event)应用启动完成后触发。这个时机比 Bean 初始化更晚一点此时所有接口已经扫描完可以安心对白名单列表做排序和归档。类里面最关键的两个成员变量一个是注入进来的RequestMappingHandlerMapping它就是 Spring MVC 保存所有 URL 映射关系的地方另一个是内部维护的ListAnonymousResource anonymousResources这个列表就是最终生成的匿名白名单。2.2 afterPropertiesSet 中如何扫描所有接口这是整个类最核心的逻辑。代码大致长这样Override public void afterPropertiesSet() { requestMappingHandlerMapping.getHandlerMethods().forEach((key, value) - { SetString urls extractUrls(key); if (CollectionUtils.isEmpty(urls)) { return; } if (value.hasMethodAnnotation(Anonymous.class)) { for (String url : urls) { anonymousResources.add(new AnonymousResource(getPattern(url), false)); } } }); }requestMappingHandlerMapping.getHandlerMethods()返回一个MapRequestMappingInfo, HandlerMethod。RequestMappingInfo里保存的是这个接口的完整映射信息包括 URL pattern、请求方式、参数条件等HandlerMethod则是真正对应的 Controller 方法。拿到HandlerMethod之后调用value.hasMethodAnnotation(Anonymous.class)判断这个方法上有没有标Anonymous。这里有个细节要注意这个判断只看方法本身如果Anonymous标在了 Controller 类上这个分支是判断不出来的。实际若依源码里还会额外处理类级注解但不同版本实现有差异后面实操章节我再展开讲。extractUrls说白了就是从RequestMappingInfo里把路径取出来。Spring MVC 很贴心地帮我们处理好了类上RequestMapping与方法上GetMapping的路径拼接所以这里拿到的 URL 是完整路径不需要自己再去拼一次。真正有点技术含量的在getPattern方法。比如你写的接口路径是/system/user/{userId}运行时真实请求可能是/system/user/1。如果白名单里存的是带{userId}的模板后面的匹配逻辑就要额外处理变量。若依的做法是用正则把{xxx}统一替换成*这样/system/user/{userId}就变成/system/user/*在matches时用 Ant 风格的*通配符就能轻松匹配任意参数。2.3 启动后排序别让通配符抢了精确路径onApplicationEvent里的逻辑相对简单就是给anonymousResources列表按照 URL 长度做降序排序代码大致是Override public void onApplicationEvent(ApplicationStartedEvent event) { anonymousResources.sort(Comparator.comparingInt(resource - resource.getUrl().length()).reversed()); }为什么要排序因为matches匹配的时候是顺序遍历列表一旦某个 URL 命中就直接返回 true。如果有两条规则一条是/*一条是/system/config/list假设请求是/system/config/list要是先匹配了/*那精确路径就被通配符吞掉了永远没有机会走到第二层判断。虽然很多匹配器内部会做尽量匹配但若依这里用的是PatternMatchUtils.simpleMatch是“匹配到就返回”的逻辑所以顺序直接影响结果。把长的、精确的路径排在前面是一种简单粗暴但非常有效的防误判手段。这一点在实战中特别容易被忽略。我有一次给某个模块的所有接口统一加了Anonymous结果同模块下一个更具体的接口怎么调都进不了登录态保护查了半天才发现是排序规则把精确路径挤到了后面。后来养成了习惯凡是自己往白名单里加通配路径都会特别留意它和已有规则之间的匹配优先级。2.4 matches 的匹配规则为什么不是简单 equals最后是matches方法public boolean matches(String requestURI) { for (AnonymousResource resource : anonymousResources) { if (PatternMatchUtils.simpleMatch(resource.getUrl(), requestURI)) { return true; } } return false; }PatternMatchUtils.simpleMatch是 Spring 自带的轻量路径匹配工具支持*匹配任意字符但不支持**跨目录匹配。若依在收集 URL 时已经做了模板变量到*的转换所以用这个工具类就够了够轻、够快也不用引一整套AntPathMatcher进来。AnonymousResource是若依自己定义的一个小类内部有两个字段url和isAuth。isAuth这个字段有点意思它表示这条匿名 URL 是否“即使带着 token 也要继续走鉴权流程”。默认情况是false也就是不管带不带 token 都直接放行。在某些二次开发场景里你可能希望一个接口允许匿名访问但如果用户带了 token又想顺便识别出身份这个字段就能派上用场。3. 它是怎么被调起来的从 TokenFilter 到 SecurityContextHolder3.1 过滤器链中的关键一票PermitAllUrlProperties本身不会拦截任何请求它是一个被动的“查询服务”。真正在请求进来时调用它的是TokenFilter这是若依-Vue 里继承OncePerRequestFilter的一个过滤器。TokenFilter的执行流程大概是这样的请求进来它先从 header 里解析 token如果 token 能换到LoginUser就把登录态放进SecurityContextHolder然后继续走后续逻辑。如果 token 解析不出来或者根本没带 token它会调用permitAllUrlProperties.matches(request.getRequestURI())判断当前地址是不是匿名白名单里的接口。命中白名单就直接调filterChain.doFilter(request, response)放行不再强制要求登录状态。没命中白名单就抛一个AuthenticationException由全局异常处理器转成 401 返回给前端。所以PermitAllUrlProperties是整个鉴权链条里一个很关键的“闸口”。这个闸口一旦失效后果很极端要么所有带Anonymous的接口全部变成需要登录要么因为某些配置错误导致整个过滤链被绕过白名单形同虚设。3.2 登录用户信息是在哪里写入的热搜里有个问题很有代表性“ruoyi在哪里写入登录用户的信息”。如果你翻过TokenFilter源码这个问题其实很好回答。在TokenFilter里拿到loginUser之后会执行类似下面这段逻辑Authentication authentication new UsernamePasswordAuthenticationToken(loginUser, null, authorities); SecurityContextHolder.getContext().setAuthentication(authentication);SecurityContextHolder底层是ThreadLocal所以每个请求的登录态是互相隔离的。一个请求从进入到返回只要线程不切换、不走异步SecurityContextHolder里的Authentication就一直有效后面的PreAuthorize(ss.hasPermi(...))就是通过读取这个上下文来判断用户身份的。但要注意如果接口走了匿名放行分支TokenFilter不会往SecurityContextHolder里写任何东西上下文就是空的。这也就解释了为什么Anonymous接口里如果再加PreAuthorize大概率会报“没有用户信息”的异常。3.3 Anonymous 与 PreAuthorize 的分工边界很多初学者会把“匿名访问”和“权限校验”混在一起以为两个是互斥的其实它们是两个不同维度的事。Anonymous解决的是“要不要登录”的问题它控制的是请求能不能不带 token 进来。PreAuthorize解决的是“登录了之后有没有权限”的问题它控制的是某个用户能不能访问某个资源。一个内部接口比如删除用户需要同时满足“已登录”和“有删除权限”这种就应该只写PreAuthorize(ss.hasPermi(system:user:remove))不要写Anonymous。一个公开的页面接口比如获取验证码它需要“不登录也能访问”这种才写Anonymous。一旦一个接口同时标注了Anonymous和PreAuthorize语义就会变模糊Anonymous说你不需要登录但PreAuthorize要求上下文里有用户信息这本身就是自相矛盾的。所以我在工作中会跟团队强调拿到需求先判断接口是“公开”还是“内部”再决定用哪套机制两个别混着写。4. 实操如何用 PermitAllUrlProperties 开放一个匿名接口4.1 最小示例方法级别加 Anonymous在若依-Vue 中想让某个接口支持匿名访问最小操作就是在 Controller 方法上标一个Anonymous注解Anonymous GetMapping(/demo/publicInfo) public AjaxResult publicInfo() { return AjaxResult.success(这是公开信息); }启动项目后直接用浏览器访问/demo/publicInfo或者用 curl 不带任何 header 请求都能正常返回 JSON不会弹出 401。如果这个接口在 Controller 类上还有一层RequestMapping(/api)那完整路径就是/api/demo/publicInfoRequestMappingInfo会自动拼接不需要你手动处理前缀。还有一点要注意如果项目配置了server.servlet.context-path比如部署在/dev-api那访问时的完整 URL 还要加上这层上下文路径。TokenFilter里的request.getRequestURI()是包含 context-path 的所以白名单里的路径也要跟着带上前缀否则永远匹配不上。4.2 类级别加 Anonymous 的坑把Anonymous写在 Controller 类上表示该类下所有方法都允许匿名访问Anonymous RestController RequestMapping(/demo) public class DemoController { GetMapping(/a) public AjaxResult a() { ... } GetMapping(/b) public AjaxResult b() { ... } }这种写法在需求上很常见比如对外数据同步接口、开放查询接口等整组方法都不需要登录态。但它有个隐患如果一个类里既有公开接口又有需要登录的接口类级注解会让后者的鉴权形同虚设等于你辛辛苦苦写的PreAuthorize全部被忽略了。少数衍生版本对类级Anonymous的支持还不一致有些版本可能压根没有解析类上的注解导致“明明标了却还是 401”。我自己的经验是尽量在方法级别加注解只有确认整个类的所有方法都必须公开时才考虑类级写法。毕竟方法级语义最清楚排错也最简单。4.3 有版本差异吗RuoYi 传统版、RuoYi-Vue、RuoYi-AI、Sa-Token 改造版这个问题我觉得值得单独拿出来讲因为太多人踩过坑。如果你用的是传统单体版 RuoYi没有PermitAllUrlProperties这个类匿名配置直接在SecurityConfig里写permitAll()。这个是第一代方案。如果你用的是 RuoYi-Vue 前后端分离版才是本文讲的这套AnonymousPermitAllUrlProperties机制。这是第二代方案。现在市面上还有很多衍生版本比如 RuoYi-AI、RuoYi-Vue-Pro以及各种基于 Sa-Token 改造的 SSO 版本。这些版本大多继承或改写了原来的鉴权逻辑。比如基于 Sa-Token 的若依可能用SaIgnore注解代替Anonymous同时把原来的TokenFilter换成了 Sa-Token 自己的过滤器。这时候你再翻代码可能根本找不到PermitAllUrlProperties被调用的地方因为鉴权过滤器整条链都被替换掉了。还有若依-Vue-Pro 里开启 BPM 工作流功能那是业务模块的扩展不影响这个配置类的定位但业务接口里哪些被放行、哪些没被放行还是值得顺着这套链路重新理一遍特别是工作流回调这类端口经常出现匿名配置漏配或过宽的问题。4.4 动态扩展白名单的一个可行思路PermitAllUrlProperties是启动时一次性扫描的想加白名单就得改代码重启服务。但实际业务中经常有这种需求运营后台想临时放行一个接口又不想发版重启。这时候可以模仿这个类的设计自己做一套数据库动态白名单。大致思路是先建一张配置表字段包括 URL 模板、启用状态、备注。启动时把启用的 URL 加载到内存缓存里同时暴露一个刷新接口后台改了配置之后调一下刷新接口或者用定时任务周期性加载。然后在TokenFilter的判空逻辑里把原来的单一判断扩展成多判断。除了permitAllUrlProperties.matches(request.getRequestURI())再加一个dynamicAnonUrlService.matches(request.getRequestURI())两者只要有一个命中就放行。代码改动不大但灵活性提升了一个量级。这里必须强调一个安全原则动态白名单的入口一定要严格做权限控制不是谁都能配置。我见过一个项目把动态白名单管理接口放在了一个公开模块里结果别人只要知道接口地址就能给自己加匿名权限等于把整个系统的安全防线给拆了。白名单这种东西宁可笨一点、慢一点也不要敞开口子。5. 常见问题与排查实录5.1 加了 Anonymous 却依然 401这是我在群里被问得最多的问题。我把排查思路整理成一个速查表遇到问题可以照着过一遍。可能原因排查方法解决思路注解标在了类上但版本不支持解析查看框架版本源码看afterPropertiesSet是否处理了类级注解改成方法级注解接口路径与白名单不匹配确认context-path是否包含在请求 URI 中在完整 URL 上做匹配必要时打印request.getRequestURI()注解加在了非 Controller 方法上确认注解是不是标在真正被 Spring MVC 映射的方法上只标在 handler 方法上不要标在普通私有方法过滤器链被其他框架替换查项目里有没有引入 Sa-Token 等第三方鉴权框架统一用第三方的放行注解或调整过滤器顺序项目上下文有多次转发或重写反向代理、网关层对 URL 做了改写在网关层同步放行规则或使用更宽松的通配符如果项目是若依-Vue 原生版本我建议先做一件事写一个临时接口在TokenFilter里加一行日志把每次请求的request.getRequestURI()和permitAllUrlProperties.matches()的结果打出来。不用猜日志会告诉你答案。5.2 路径匹配不上或匹配了不该匹配的拿到白名单之后先留意*和**的区别。若依把{xxx}替换成的是*而 Spring 的PatternMatchUtils里*只能匹配路径中一个层级不跨/。比如/system/user/*能匹配/system/user/1但匹配不了/system/user/1/2。如果你希望整个子目录都匿名手写的 URL 应该用/system/user/**。还有一种常见情况是精确路径和通配符互相干扰。例如你已经有一条/system/**的通配规则又单独放行了/system/user/list由于列表是按 URL 长度降序排序的/system/user/list会更早被匹配所以结果反而是正常的。但如果通配路径写得太宽把本来要保护的接口也吞进去这个问题就麻烦了。排查思路很简单把当前系统的匿名白名单完整打印出来逐条看有没有“看起来不该公开”的路径。若依的anonymousResources是内存列表你可以在onApplicationEvent或者matches方法里临时加一行日志把列表内容输出到控制台一目了然。5.3 多模块项目里 Controller 扫描不全前后端分离的大型项目经常拆成多个 Maven 模块。如果你的 Controller 不在启动类默认扫描的包路径下就不会被注册到 Spring 容器里RequestMappingHandlerMapping自然拿不到这个接口Anonymous就算标了也不会被扫描到。排查时先看启动类上的SpringBootApplication默认扫描范围再看有没有自定义ComponentScan。如果业务模块放在com.company.business这种和启动类不同根的包下就需要显式扩展扫描路径。这是很基础的问题但往往藏得很深因为接口本身是能访问的只是白名单扫描漏了表现就是“同一个Anonymous有的接口生效有的不生效”。5.4 接入 Sa-Token / SSO / BPM 后白名单失效如果你在若依上接入了 Sa-Token 或者做了 SSO 改造要意识到一个事实过滤器链变了原来的TokenFilter可能已经被替换掉PermitAllUrlProperties也就变成了一个没人调用的“僵尸配置类”。这时别去改PermitAllUrlProperties的源码应该找到新的鉴权过滤器里对应放行的那个判断逻辑看它有没有兼容旧的Anonymous没有的话就加入兼容处理或者干脆统一迁移到新注解。SSO 场景里特别要关注回调地址比如第三方系统登录成功后跳回本系统的地址。这个回调通常要求匿名访问但这种匿名应该精确到具体的回调路径而不是直接把/**整条放开。我见过一个项目为了调试方便把整个系统都设成了匿名上线后忘改回来相当于所有接口都裸奔了一段时间还好是内网系统否则后果不堪设想。5.5 如何写一个简单的自动化测试来守住这条链框架改来改去最容易出问题的环节就是匿名白名单。我建议在项目里针对这个点写几个接口测试守住底线。用 Spring Boot Test 配合 MockMvc最简单的一组用例是这样SpringBootTest AutoConfigureMockMvc public class PermitAllUrlTest { Autowired private MockMvc mockMvc; Test public void anonymousUrlShouldPassWithoutToken() throws Exception { mockMvc.perform(get(/demo/publicInfo)) .andExpect(status().isOk()); } Test public void protectedUrlShouldRejectWithoutToken() throws Exception { mockMvc.perform(get(/system/user/list)) .andExpect(status().isUnauthorized()); } }第一条规定了白名单接口必须能匿名访问第二条规定了受保护接口没 token 时必须拒绝。这两条用例只要跑通说明过滤链的基本行为没问题。以后谁在改造鉴权逻辑时误删了匿名判断测试直接就会报红避免问题流到生产环境。这里有个小技巧测试类尽量用AutoConfigureMockMvc它会自动配好 Spring Security 的 Mock 环境不需要额外起真实端口跑起来很快。如果你改动了过滤器链再补几个“带 token 访问受保护接口”的用例整个鉴权链路就基本有保障了。5.6 最容易忽视的Anonymous 接口里的业务代码前面讲的都是框架层面的问题最后补一个业务层面的坑。有些接口标了Anonymous但从 Redis 里拿用户信息的代码却依然写在业务方法里。匿名请求没有登录态LoginUser从SecurityContextHolder里取出来是 null然后业务代码直接 NPE。这种问题框架层面没有任何提示只有接口被匿名访问时才会暴露。我的建议是凡是标了Anonymous的接口业务逻辑里就不要再依赖任何用户上下文。如果有部分逻辑需要当前登录用户那就拆成两个接口一个匿名版一个登录版用不同的地址区分开。这样既保证了匿名接口能正常调用也不会让登录态缺失的逻辑在不知不觉中出问题。6. 我在二次开发中最后想提醒的三件事6.1 别把匿名接口当成“免检通道”匿名接口不是说“写个注解就完事了”它意味着这个接口会暴露给任何能访问到你系统的人。登录、验证码这种接口放行没问题但涉及用户数据、系统配置、敏感操作的接口一定要慎之又慎。我见过一个项目把个人信息的查询接口标了Anonymous本意是让小程序端不登录也能查到基本信息结果接口返回的是完整手机号和身份证号等于把用户隐私直接挂在公网上。白名单是用来解决“无法带 token”的问题的不是用来绕过权限设计的。真正合理的做法是需要公开的数据只返回公开字段或者改成带 token 的登录查询。6.2 上线前用全局搜索扫一遍所有 Anonymous这个习惯我保持了很长时间朴素但非常有效。每次提测或者上线前在 IDE 里对Anonymous做一次全局搜索逐个确认这些接口是否真的应该匿名。尤其是项目经历过多次迭代之后往往会出现一种情况某个接口早期为了联调方便加了Anonymous后面功能正式上线了注解却一直没删。这种“历史遗留匿名接口”是最危险的因为做功能开发的人已经忘了它存在安全测试的人也没注意到它就在角落里默默公开着。如果搜索出来的注解数量比较多建议在项目里单独建一个文档维护白名单清单把每个匿名接口的用途、开放时间、负责人记下来。流程听起来繁琐但真出了事这张表能帮你快速定位是谁、什么时候、为什么开的这个口子。6.3 多租户和 AI 衍生框架的匿名边界要额外小心现在很多项目是基于若依做多租户改造的或者接入了 AI 能力变成 RuoYi-AI 这类衍生框架。这些场景下匿名接口的边界问题会被放大。多租户项目里租户信息通常是从 token 里解析tenantId拿到的。匿名请求没有 token也就没有租户上下文业务代码如果默认“当前租户 0”或者“默认租户”就可能出现 A 租户的公开接口返回了 B 租户的数据。遇到这种场景匿名接口要么不做租户隔离要么在网关层根据 IP、域名等外部标识做租户识别。接入 AI 功能的产品还容易踩另一个坑为了让用户先试后登把提问、对话这类接口全部匿名。这本身没问题但要注意调用外部 AI 服务时成本是真实发生且不可控的。我的经验是匿名接口必须配套限流和频控哪怕只是最简单的 IP 维度限流也能在大流量冲击时保住成本底线。回到最开始那个问题PermitAllUrlProperties表面上只是一个“扫描注解、收集 URL、匹配请求”的小工具但理解了它你其实就理解了若依整个鉴权链路的入口逻辑。我自己在做若依二次开发时最深的一个体会是白名单这种东西配置越分散、越隐蔽越容易出问题。尊重Anonymous加统一扫描这套模型把规则尽量收敛到一个清晰的清单里排错时才能真正做到心中有数。
返回列表