资讯动态

若依接口无授权访问排查与修复:从Security到拦截器

发布时间:2026/9/10 8:48:27 来源:尧图企业网站定制
搞过若依的朋友应该都遇到过这么个情况明明在 SecurityConfig 里把某个接口配成了 permitAll结果前端调的时候直接 401反过来有些接口啥都没配居然也能不登录直接访问。还有更隐蔽的——菜单里没配按钮权限但只要你知道了接口路径直接拿 URL 一敲数据照样返回。这套“无授权接口访问”的问题说大不大说小不小但一旦漏出去基本等于把后门写在明面上。我前后基于若依RuoYi做过好几个项目从单体版到前后端分离版再到 RuoYi-Plus 这种增强版都踩过一遍。这篇文章就把我在实际排查和改造过程中积累的东西全整理出来——先说清楚若依的接口授权到底分几层、再讲常见的无授权访问会出现在哪些地方、然后是完整的排查方案和修复实操最后给大家一份可以在生产环境直接抄作业的接口白名单配置参考。如果你是刚接手若依项目或者正在做安全管理相关的工作这篇文章应该能帮你省不少事。1. 若依接口授权体系到底是怎么设计的很多刚接触若依的人会以为“登录了就能调接口”其实只对了一半。若依的接口访问控制是三层叠加的从外到内依次是 Spring Security 的 URL 级过滤、拦截器层面的权限校验、以及业务代码里的注解和数据权限。这三层各管一摊少了哪一层都可能出现“无授权访问”的问题。1.1 Spring Security 是入口但不是全部在若依框架里Spring Security 承担的是最外层的身份认证和 URL 授权任务。核心配置在SecurityConfig类中里面的filterChain方法把所有请求分为三类白名单接口无认证可访问、已登录用户可访问的接口、需要特定角色或权限的接口。白名单接口通过permitAll()放行典型的是登录接口、验证码接口、注册接口。剩下的接口统一走anyRequest().authenticated()意思就是“你得先登录”。但注意authenticated()只要求“你是登录用户”它不检查“你有没有权限调这个接口”。所以到这里就能看出第一层问题的根源Spring Security 只解决“你是谁”的问题不解决“你能不能干这件事”的问题。真正干活的还在后面。1.2 拦截器做权限判断的时机如果说 Spring Security 管的是“门卫”那若依的SecurityInterceptor拦截器管的就是“楼层门禁”。它会在请求进入 Controller 之前通过自定义注解PreAuthorize去校验当前用户是否具备对应的权限标识。若依框架里权限标识通常长这样PreAuthorize(ss.hasPermi(system:user:list))。这个注解的意思是当前用户必须有system:user:list这个权限标识才能往下走。而这个权限标识怎么来的是你登录后后端从sys_menu表里查询该用户的权限字符串然后放进用户信息里的。拦截器拿到请求后会解析 Controller 方法上的注解字符串然后去当前用户的权限集合里比对。有就放行没有就抛异常返回 403。这里有个关键点如果一个接口方法上没加PreAuthorize注解那么拦截器就直接放行了因为若依的拦截器默认逻辑是“没有注解就不做权限校验”。这就是第二层漏洞的高发区。1.3 数据权限是最后一道闸RuoYi-Plus 以及不少二次开发版本里还引入了数据权限的控制。它管的是“你有权限看哪些数据”比如部门数据、自定义数据范围等。在 Service 层通过DataScope注解配合数据权限拦截器自动拼接 SQL 过滤条件。数据权限缺失的表现为A 用户能看到 B 部门的数据但 B 部门的数据理论上不应该对 A 可见。这种问题通常不会被“无授权接口访问”直接暴露出来但一旦接口权限层出了问题数据权限就更拦不住了。所以一个接口真正“无授权可访问”要穿透这三层。实际上大多数出事儿的接口都是因为第一层被放行、第二层没有注解兜底两层同时失守。2. 无授权接口访问的典型场景与根因分析我梳理了自己项目里遇到过的几类无授权访问问题基本都是可以在网上搜到同类提问的高频场景。只有知道了它们是怎么来的后面才能对症下药。2.1 接口没配置在 SecurityConfig 白名单里却能匿名访问先看一个怪现象SecurityConfig里明确没有放行的接口居然能在不登录的情况下直接访问而且返回 200。这种问题的根子一般不是配置写错了而是静态资源映射和接口路径发生了重叠。Spring Security 的过滤链默认会放行静态资源比如/index.html、/css/**、/js/**、/favicon.ico等。如果你把某些接口路径设计成了/static/**、/js/**这种样子就会被静态资源处理器直接接管绕过了权限校验。我接手过一个项目同事把导出接口放在/static/export/**下面说这样浏览器直接访问路径就能下载文件。结果 Security 的permitAll确实没配这个路径但静态资源配置里把它当资源目录放行了导致任何人只要拼对了参数就能导出全部数据。另外还有一种情况是把接口 Controller 的 RequestMapping 写成了/**这种通配路径导致所有请求都进了这个 Controller然后又因为没有PreAuthorize注解直接执行了业务逻辑。这种属于设计缺陷排查起来也比较隐蔽。2.2 SecurityConfig 配了 permitAll 但接口其实需要登录这个场景和上面的问题正好相反是“过度放行”。典型代码如下.antMatchers(/system/user/**).permitAll()这行的本意是放行某个不需要登录的接口但路径通配符/system/user/**把整个用户模块都放行了。用户列表、用户详情、删除用户、重置密码这些全部暴露在公网。这种问题在多人协作项目里特别常见有人图省事直接配了大范围的路径匹配。而在若依的默认配置里本来就有类似/login、/captchaImage这种精确路径正确做法也是精确匹配到具体接口而不是用模块级通配符。我见过更极端的把.anyRequest().permitAll()写在配置末尾也就是直接关掉了整个 Spring Security 的认证开关。这种配置在本地调试时很方便但一旦被推到生产环境所有接口都裸奔了。这不是危言耸听网上搜若依相关的问题能搜到不少把anyRequest().permitAll()提交到仓库的反面教材。2.3 接口加了 PreAuthorize 注解但没有配置对应权限这类问题多出现在代码生成器生成的模块上或者二次开发时新加的 Controller 里。若依的代码生成器默认会为每个方法生成PreAuthorize(ss.hasPermi(system:xxx:list))这类注解权限标识由“模块名 实体名 操作类型”拼接而成。但如果你一开始没走代码生成器而是自己手写 Controller很容易漏掉这个注解。漏掉以后登录用户都能调而再配合登录接口被爆破或者 token 泄露就麻烦了。还有一种情况是注解加上了但这一条权限字符串并没有配置到sys_menu表里。也就是说数据库里不存在这个权限标识对应的按钮用户就算拥有所有菜单权限集合里也不会有这个标识。表现出来就是管理员都访问不了这个接口报 403。这不算“无授权访问”但对于管理员来说等于是被锁在门外。2.4 自定义注解和全局权限处理策略没生效部分基于若依二次开发的项目会自定义一套权限注解比如RequiresPermission然后通过 AOP 做切面校验。这时候如果 AOP 切面没生效或者切点表达式没匹配到新的 Controller 包路径那这个注解就形同虚设。我遇到过的情况是新模块放在了com.xxx.project.xxx.controller包下面而自定义切面只切了com.ruoyi.project.*.controller这个包。结果新模块的接口全部绕过了权限校验。这种问题如果不做全接口扫描测试基本很难发现。2.5 匿名内部接口和回调接口被误放行还有一种比较特殊但风险极高的场景为了对接第三方系统在接口上加了Anonymous注解RuoYi-Plus 里有这个注解RuoYi 单体版也支持自定义匿名注解或者在 SecurityConfig 里放行了一批回调 URL。这类接口本身就是为了给外部系统调用不要求用户登录但如果没做签名校验、IP 白名单、调用频率限制这些额外手段就会变成无授权访问。很多对接场景里开发者只考虑了“能调通”没考虑“谁都能调”。我之前对接过一个支付回调接口内容是把订单状态改成已支付。Security 里配了permitAll但回调接口没有验签逻辑。结果测试阶段被同事拿 Postman 手动调了一下订单直接变已支付了。这种属于业务逻辑漏洞稍后在防范策略里我会单独讲。3. 从现象到根因一套可落地的排查方案排查无授权接口访问最忌讳一上来就改代码。正确的打开方式是先观察、再定位、最后针对性修复。下面这套排查流程我用了很多次基本能应对绝大多数情况。3.1 第一步确认请求到底有没有经过 Spring Security打开若依后端控制台在SecurityConfig的filterChain方法里临时加一行日志打印打印出当前请求的 URI。或者更简单——看若依启动时控制台输出的日志级别把 Spring Security 的日志级别调整到 DEBUGlogging: level: org.springframework.security: DEBUG改完后启动项目直接访问有问题的接口观察日志中是否出现Securing POST /xxxFiltered request或AuthorizationFilter相关的判定记录如果日志里根本没有这个接口的路径那说明请求压根没进入 Spring Security 的过滤链。最可能的原因是静态资源配置吃了这个路径或者请求到了其他 Servlet 处理器比如 WebSocket、文件上传 Servlet。如果日志里有路径但是显示permitAll直接放行那就去查 SecurityConfig 里的白名单配置。这一步的产出是确定问题出在安全链路之外还是安全链路之内。3.2 第二步检查拦截器有没有被正确注册若依的权限校验核心是SecurityInterceptor它实现了 Spring MVC 的HandlerInterceptor。在 WebMvcConfigurer 的addInterceptors方法里注册。常见问题是新加的 Controller 用了RestController但没走统一路径前缀比如接口路径是/openapi/**而拦截器注册时只拦截了/**以外的路径或者反过来拦截器注册时排除了/openapi/**但业务上这个路径并不应该被排除。拦截器注册了但excludePathPatterns里配了过宽的路径比如排除了/xxx/**整个模块。排查方法很简单在拦截器的preHandle方法里加个日志打印当前请求 URI 和用户信息。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(拦截到请求: request.getRequestURI()); // ... 原有逻辑 }如果你访问问题接口时控制台没有打印这行日志说明拦截器根本没覆盖到该请求。这时候就要去检查拦截器注册的拦截路径和排除路径。3.3 第三步逐个接口做“未登录调用测试”上面两步是静态排查这一步是动态测试。建议用 Postman 或者 Apifox 建一个“未登录”环境不带任何 Token逐个调用你关心的接口。先调用列表接口再调用详情、新增、修改、删除类接口。对于返回 401 的接口说明 Spring Security 层拦住了对于返回 200 或 403 的接口再做进一步区分返回 200 且带出数据说明接口被彻底放行了问题很严重需要立即修复。返回 403说明没有权限标识三层体系中至少有一层拦住了一部分但需要确认是哪一层。返回 401 但页面实际能访问可能是前端用了其他认证方式先确认前端使用的是不是若依标准的 Authorization Header。做测试的时候记得对每个测试的接口和结果做记录方便后面汇总修复。3.4 第四步借助接口扫描工具做全量盘点如果你负责的项目接口特别多手工一个个测肯定不现实。这时候可以用接口扫描工具。常用的方案有SpringDoc/Swagger 接口列表 脚本批量调用若依集成 Swagger 后可以拿到所有接口的路径和请求方式写个 Python 脚本批量发未登录请求统计响应码。Burp Suite / Yakit这类安全测试工具可以做被动扫描和主动扫描能模拟未登录状态抓流量、测接口。自定义 Test 接口在测试环境写一个 Spring Boot Test 类注入 MockMvc批量模拟未认证、已认证无权限、已认证有权限三种场景。如果你用的是 RuoYi-Plus 或者 RuoYi-Vue-Plus它自带了 Swagger 接口文档导出接口清单很方便。用 Python 脚本批量调用时大致逻辑如下import requests base_url http://localhost:8080 # 从 swagger json 里读取 paths swagger requests.get(base_url /v3/api-docs).json() unauthorized_apis [] for path, methods in swagger.get(paths, {}).items(): for method in methods.keys(): if method.lower() not in [get, post, put, delete]: continue url base_url path # 替换 path 参数占位符 # 这里简单处理实际需要根据 param 定义填充 resp requests.request(method.upper(), url, timeout5) if resp.status_code 200: unauthorized_apis.append((method.upper(), path, resp.status_code)) print(unauthorized_apis)注意这只是未登录测试的粗略版本真正跑之前要处理路径参数、以及接口之间的依赖关系。但是作为快速盘点已经足够了。4. 核心修复实操给接口授权这件事的完整步骤排查出问题后修复方案要分情况讨论。我把实践中用到的方案整理成几个层面越底层越治本。4.1 修正 SecurityConfig 的路径匹配规则首先是精确化白名单。不要用/system/user/**这种大范围匹配而是把需要放行的接口缩小到具体路径。比如你只需要放行“用户注册”接口那就写.antMatchers(/system/user/register).permitAll()同时把anyRequest().authenticated()保留确保所有非白名单接口必须登录。如果你已经存在过度放行的路径还需要检查一下excludePathPatterns里的排除项。若依排除的路径通常是/login、/captchaImage、/register、静态资源路径等不要随意新增模块级排除。另外如果你的项目是用 Spring Security 的新版本Spring Boot 2.7 / Spring Security 5.8注意antMatchers已经被标记过时建议改用requestMatchers.requestMatchers(/login, /captchaImage).permitAll() .requestMatchers(/system/user/register).permitAll() .anyRequest().authenticated()4.2 给 Controller 方法补齐 PreAuthorize 注解排查完 Spring Security 一层之后第二层要做的事情是把所有缺少权限注解的接口补上注解。如果这些接口本来就是后台管理专用接口那就给它们加上标准的权限标识PreAuthorize(ss.hasPermi(system:user:list)) GetMapping(/list) public TableDataInfo list(SysUser user) { startPage(); ListSysUser list userService.selectUserList(user); return getDataTable(list); }权限标识的定义统一放到数据库sys_menu表里确保权限标识全局唯一。给菜单分配完成后再给角色勾选对应菜单权限用户才能正常访问。如果你不确定权限标识应该怎么命名可以参考若依默认的规则模块名:实体名:操作类型例如system:user:add、system:role:edit、system:post:remove。这样的命名在排查权限问题时非常直观。4.3 对必须匿名访问的接口做精细管控有些接口就是得允许未登录访问的比如获取验证码、注册、第三方登录回调。这些接口不应该被一刀切地放进permitAll而是要做精细化管控。推荐使用若依的Anonymous注解方式。以 RuoYi-Vue-Plus 为例在 SecurityConfig 中配置了AnonymousAuthenticationFilter相关的逻辑Controller 方法上加Anonymous就能跳过认证同时不会被拦截器拦截。但注意Anonymous只是解决了“能不能访问”的问题它没有解决“访问者是不是真身”的问题。这类接口必须加额外的防护至少要有验证码或者短信验证码请求签名APP 端常用IP 白名单 / 调用频率限制比如注册接口验证码必须放在 Redis 里做有效期校验回调接口要验证来源 IP 和签名。否则虽然接口从“未登录可访问”变成了“登录用户可访问”本质上还是有很大的风险敞口。4.4 兜底方案自定义 Filter 做接口安全审计修复完已知的问题后还可以部署一道“兜底防线”实现一个简单的 Filter 或者 AOP 切面对所有请求做安全审计记录。我自己在生产项目里就加过一个SecurityAuditFilter逻辑很简单打印请求路径、请求方法、来源 IP、用户 ID、请求时间对非白名单接口检查是否携带了合法 Token如果 Token 不存在或者无效记录告警日志对于返回 200 的无认证请求触发统计报警这个 Filter 不是为了替代 Spring Security而是为了后续排查问题时有日志可查。很多时候等到发现接口数据泄露的时候攻击者已经扫了很多天了没有审计日志根本没法复盘。4.5 RuoYi-Plus 的扩展安全配置如果你用的是 RuoYi-Plus微服务版本或者前后端分离增强版它的 Security 配置相对复杂一点因为涉及多个微服务模块的权限认证。RuoYi-Plus 的权限校验核心是Sa-Token或者 RuoYi-Vue-Plus 里集成了 Sa-Token不再使用 Spring Security。它的路由过滤配置在SaInterceptor和相关配置类里。你需要在网关或者每个服务里检查白名单配置是否合理拦截器注册的路径是否正确是否需要开启Sa-Token的注解鉴权RuoYi-Plus 的接口鉴权使用SaCheckPermission(system:user:list)这种写法和若依单体版的ss.hasPermi不一样。排查思路相通但配置位置和语法有差异不要拿单体版的方案直接套。5. 上线前必做的安全自检清单与测试脚本修好之后不是就完事了。为了确保以后不会再次出现同类问题我整理了一份上线前安全自检清单。把它打印出来照着一个个打勾基本能拦住 90% 以上的无授权访问漏洞。5.1 接口授权检查清单检查项检查方法通过标准全量接口未登录访问测试脚本或 Postman 批量请求除白名单外均返回 401/403白名单接口复核人工核对 SecurityConfig每个白名单接口都有明确业务理由登录用户越权测试使用低权限账号调用高权限接口返回 403数据权限测试不同部门用户查列表和详情看不到非授权数据静态资源路径不能映射接口检查 WebMvcConfigurer接口路径不与静态资源前缀重合审计日志可用性查看日志输出请求路径/状态码/用户ID完整记录Swagger 接口文档安全生产环境关闭或权限控制未登录无法访问文档页匿名回调接口签名校验检查回调代码无签名或 IP 校验则不合格5.2 批量未登录测试脚本的参考实现上面提到的 Python 测试脚本我补充一个更完整一点的版本方便有需要的人直接用。这个脚本会读取若依的application.yml里配置的接口前缀然后把 GET/POST 请求分别发出去记录所有返回 200 的接口。import json import requests BASE_URL http://127.0.0.1:8080 AUTH_EXEMPT_PATHS {/login, /captchaImage, /register} def load_api_docs(): # 若依单体版 Swagger 2 路径是 /v2/api-docs # RuoYi-Plus 的 knife4j 路径通常是 /v3/api-docs for doc_path in [/v3/api-docs, /v2/api-docs]: try: resp requests.get(BASE_URL doc_path, timeout10) if resp.status_code 200: return resp.json() except Exception: continue return None def replace_path_params(path, method_item): # 把 {id} 之类的参数替换为测试值 # 这里只处理常见的路径格式实际需要按接口定义补齐 for param in method_item.get(parameters, []): if param.get(in) path: name param.get(name) param_type param.get(schema, {}).get(type, string) if param_type integer: path path.replace({ name }, 1) else: path path.replace({ name }, test) return path def main(): docs load_api_docs() if not docs: print(未获取到API文档) return dangerous [] normal [] paths docs.get(paths, {}) for path, methods in paths.items(): for method, detail in methods.items(): if method.lower() not in [get, post, put, delete]: continue if path in AUTH_EXEMPT_PATHS: continue real_path replace_path_params(path, detail) url BASE_URL real_path try: if method.lower() get: resp requests.get(url, timeout5) else: resp requests.post(url, json{}, timeout5) if resp.status_code 200: dangerous.append((method.upper(), path, resp.status_code)) else: normal.append((method.upper(), path, resp.status_code)) except Exception as e: print(f请求异常: {method.upper()} {path} - {e}) print( 未登录可访问的接口 ) for item in dangerous: print(item) print(f\n共发现 {len(dangerous)} 个风险接口) print(f正常拦截接口数: {len(normal)}) if __name__ __main__: main()这个脚本跑出来的结果只能作为“疑似风险”的参考不能直接作为定论。因为有些接口虽然返回 200但其内部会做二次校验比如参数校验不过直接返回错误提示这种情况不算真正的权限漏洞。但它能快速帮你圈定重点排查对象。5.3 上线前的最后闭环上线前还有一件容易被忽略的事把接口文档入口在生产环境关掉。很多人开发时图方便Swagger 或者 Knife4j 一直开着部署到生产环境也不关。实际上接口文档本身就是攻击者的地图有了它你的所有接口路径、参数、请求模型全都暴露了。若依默认配置里生产环境可能已经通过application-prod.yml做了开关控制但你最好确认一下。比如在配置里加上knife4j: production: true这样生产环境就不会展示接口文档页面。如果项目里用 SpringDoc可以这样关闭springdoc: api-docs: enabled: false swagger-ui: enabled: false6. 常见问题速查与避坑经验最后这部分是纯干货都是我实际踩坑踩出来的经验遇到类似问题可以直接对照排查。6.1 接口返回 200 但前端拿不到数据这种情况通常不是权限问题而是接口路径被静态资源处理了或者返回了 HTML 内容。你在浏览器直接访问接口 URL看返回内容是不是一个 HTML 页面如果是大概率是被前端路由或者静态资源配置拦截了。排查方向放在 WebMvcConfigurer 的 addResourceHandlers 配置上。6.2 接口返回 401 但系统里明明有这个用户如果你确认账号密码正确、用户状态正常但还是 401先看是不是密码加密方式变了。若依单体版默认用 BCrypt但某些老项目改用了 MD5。新老代码混用的时候登录接口对不上存储密码的加密方式就会出现这种情况。另外排查 Redis 里有没有存用户 Token缓存被清空后登录用户也会被认为未认证。这种情况下删掉 Redis 缓存再重新登录就能解决。6.3 菜单里有按钮但是接口报 403这是我在二次开发项目里遇到最多的问题。菜单和按钮权限在sys_menu表里是两条记录菜单是目录类型按钮是按钮类型。如果角色只分配了菜单没有分配按钮用户就看不到按钮、也没法调用对应的接口权限校验。排查 SQL查看用户的权限字符串里有没有sys:user:add这种按钮权限。没有的话去角色管理里重新分配按钮权限。还有一种情况是代码改动后权限标识变了比如从system:user:add改成了system:user:create但数据库菜单里还是旧值。6.4 匿名接口被暴力请求如果某个匿名接口没有做限流就很容易被刷。轻则消耗服务器资源重则被用来撞库、注册垃圾账号。我的建议是所有匿名接口至少要接验证码和 IP 限流。若依的RedisCache可以很方便地实现简单的计数器限流或者接入 Sentinel 这类限流组件。不要觉得麻烦上线之后出问题再补代价会大得多。6.5 新增模块后权限全部失效有时候你会遇到这种情况项目原本好好的开发了一个新模块之后所有接口权限都失效了。排查思路从这两点入手新模块的包路径是否在 Spring Boot 扫描范围内如果新模块不在SpringBootApplication所在包的扫描路径下SecurityConfig 和拦截器可能没有被正确加载导致安全配置失效。新模块是否引入了自己的 Spring Security 配置类或者依赖传递里引入了其他安全框架比如把 Spring Security 依赖排除掉了。这类问题属于框架层面的配置冲突排查看报错日志往往比看代码更高效。6.6 前后端分离下 Token 被前端缓存泄露最后讲一个案例。有个项目把 Token 存在 localStorage 里XSS 漏洞一被打穿攻击者直接拿到 Token 冒充管理员。这虽然不是“无授权接口访问”的直接问题但却是无授权访问最常见的手法之一——用别人的合法身份调接口。我后来在项目里是这么处理的Token 存活时间调短比如 30 分钟配合 Refresh Token敏感操作删除、修改密码、导出强制校验二次认证前端不要用 localStorage 存 Token改成 HttpOnly CookieHttpOnly Cookie 加上 Secure 标志后JS 脚本读取不到能在很大程度上防止 XSS 带来的 Token 泄露。不过要注意跨域和 CSRF 的平衡若依前后端分离默认用 Token 而不是 Cookie改造起来会有一些额外工作量。写在最后的项目体会关于无授权接口访问这个主题如果我只能说一句最核心的经验那就是接口安全不是配一个注解就完事的事它应该是一个贯穿开发、测试、上线的持续动作。我在不同项目里反复见到同一个问题——开发阶段觉得“这个接口内部用不对外不用加权限”于是留下一个又一个没有授权的接口。等部署到生产环境测试的人没有覆盖到最后被爬虫或者恶意请求钻了空子。这个问题之所以反复出现根子不在于技术方案而在于流程里缺乏“每次上线前做一次全量接口权限扫描”这一步。所以如果你现在正在维护一个若依系统我建议你在下一次发版前按照这篇文章的排查清单走一遍。说实话整个过程花不了半天时间但能帮你把系统里那些“裸奔”的接口统统揪出来。等这些东西都收拾干净了再去看那些复杂的微服务网关鉴权、细粒度数据权限你心里才有底。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价