跳转到帖子

背景

最近在做内部系统安全复盘时,遇到一个比较典型的问题:某业务后台已经接入统一登录,但部分管理接口仍然可以在未登录状态下被访问。问题本身不复杂,却很有代表性,涉及路由鉴权、前后端权限边界、接口设计习惯以及代码审计方法。

这类问题在中小型后台、快速迭代的运营系统、历史项目迁移中比较常见。前端页面可能已经做了登录拦截,菜单也按角色隐藏,但后端接口如果没有统一鉴权,实际风险仍然存在。

本文只讨论防御视角下的漏洞分析和修复思路,不涉及针对真实目标的攻击利用过程。示例均为本地测试环境中的抽象代码。

问题场景

系统大致结构如下:

  • 前端:Vue 单页应用,通过路由守卫判断是否存在 token。
  • 后端:Spring Boot 提供 REST API。
  • 登录:统一认证服务签发 JWT。
  • 权限:部分接口使用注解校验,部分接口依赖网关或前端控制。

问题接口位于后台管理模块,例如用户列表、配置查询、导出任务状态等接口。页面层面无法直接进入,但接口在缺少登录态时仍返回数据。

典型表现包括:

  • 访问管理页面会跳转登录页,但直接访问接口仍返回 200。
  • 同一 Controller 中部分方法有权限注解,部分方法没有。
  • 新增接口未纳入统一拦截器白名单/黑名单策略。
  • 测试环境配置被复制到生产环境,导致部分路径被放行。

技术分析

这类未授权访问通常不是单点代码失误,而是权限模型不清晰导致的工程问题。常见成因有以下几类。

1. 只在前端做登录拦截

前端路由守卫只能提升用户体验,不能作为安全边界。比如常见的写法:

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')
  if (!token && to.path !== '/login') {
    next('/login')
  } else {
    next()
  }
})

这段逻辑只能阻止普通用户通过页面导航访问后台,无法约束后端 API。只要后端没有校验,接口仍然是暴露的。

2. 后端鉴权策略不统一

在 Spring Boot 项目中,经常看到两种混用方式:

  • 部分接口通过拦截器校验 token。
  • 部分接口通过注解如 @PreAuthorize 控制权限。
  • 少量历史接口完全没有接入权限体系。

例如:

@RestController
@RequestMapping("/admin/user")
public class AdminUserController {

    @PreAuthorize("hasAuthority('admin:user:list')")
    @GetMapping("/list")
    public List<UserVO> list() {
        return userService.listUsers();
    }

    @GetMapping("/detail")
    public UserVO detail(Long id) {
        return userService.getUserDetail(id);
    }
}

上面 /list 有权限校验,但 /detail 没有。如果项目没有全局认证拦截,/detail 就可能成为未授权访问点。

3. 白名单配置过宽

另一个常见问题是认证拦截器或网关白名单配置过于宽泛:

security:
  ignore-paths:
    - /login
    - /captcha
    - /actuator/**
    - /admin/**/export
    - /admin/config/**

配置初衷可能是放行登录、验证码、健康检查,或者临时调试导出接口。但随着业务迭代,某些路径下新增了敏感接口,白名单规则没有同步收敛,最终导致过度放行。

尤其需要注意通配符规则,例如 /admin/**、/**/export、/api/public/**。这类配置短期方便,长期风险较高。

4. 网关鉴权和服务鉴权边界不清

有些系统将认证逻辑放在 API Gateway 或 Nginx 层,后端服务默认信任来自内网的请求。这种架构不是不能用,但必须保证:

  • 后端服务不能被外部直接访问。
  • 网关转发时要传递可信身份信息。
  • 服务端仍需对关键操作做权限校验。
  • 内部调用也要有明确的认证边界。

如果后端服务端口误暴露,或者测试环境与生产环境网络策略不一致,就容易出现绕过网关直接访问服务接口的问题。

代码审计关键步骤

针对这类问题,建议不要只靠手工点页面,而是结合路由、配置、注解和日志做系统性排查。

1. 梳理接口清单

优先从后端路由入手,导出所有 Controller 映射关系。Spring Boot 可以通过 Actuator 或启动日志辅助查看,生产环境不建议直接开放相关端点。

# 本地测试环境查看接口映射
curl  Actuator,也可以用 IDE 搜索:

  • @RequestMapping
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

重点关注路径中包含以下关键词的接口:

  • admin、manage、system
  • user、role、permission
  • config、setting
  • export、download
  • log、audit

2. 对比鉴权注解覆盖情况

可以用简单脚本或 IDE 结构视图检查 Controller 方法是否缺少鉴权注解。不是所有方法都必须写注解,但管理类接口至少应有统一认证兜底。

常见需要检查的注解包括:

  • @PreAuthorize
  • @Secured
  • @RolesAllowed
  • 项目自定义注解,例如 @RequiresPermission、@AuthCheck

一个相对稳妥的做法是:全局先校验登录态,再由方法级注解校验具体权限。也就是认证和授权分层处理。

3. 检查拦截器与白名单

重点看 WebMvcConfigurer、Filter、SecurityConfig、Gateway Filter 等位置。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/login",
                        "/captcha",
                        "/health"
                );
    }
}

这里的原则是:默认拦截,明确放行。放行路径越少越好,且需要定期审查。

不建议使用下面这种配置:

.excludePathPatterns(
    "/admin/**",
    "/api/**/public/**",
    "/**/download/**"
);

如果确实存在公开下载或公开查询接口,建议单独放在明确的 public Controller 下,避免与后台管理接口混用路径。

4. 验证不同身份下的访问结果

测试时至少准备三类请求:

  • 无 token 请求。
  • 普通用户 token 请求。
  • 管理员 token 请求。

预期结果应当清晰:

请求身份后台列表接口个人信息接口公开验证码接口
无 token401401200
普通用户403200200
管理员200200200

这里要区分 401 和 403:

  • 401 Unauthorized:未认证,缺少有效身份。
  • 403 Forbidden:已认证,但权限不足。

很多系统统一返回 200 再用业务码表示失败,这种方式不是完全不可行,但会增加网关、监控和自动化测试的判断成本。新项目建议尽量使用标准 HTTP 状态码。

修复建议

1. 建立默认拒绝策略

认证策略建议采用默认拒绝,而不是默认放行。除登录、验证码、静态资源、健康检查等明确公开接口外,其余接口都必须先通过认证。

http
  .authorizeHttpRequests(auth -> auth
      .requestMatchers("/login", "/captcha", "/health").permitAll()
      .anyRequest().authenticated()
  );

如果项目使用 Spring Security,这是比较清晰的基线。对于自研拦截器,也应保持类似逻辑。

2. 关键接口增加方法级授权

只校验登录态还不够,后台管理接口应继续校验角色或权限点。

@PreAuthorize("hasAuthority('system:user:read')")
@GetMapping("/admin/user/detail")
public UserVO detail(@RequestParam Long id) {
    return userService.getUserDetail(id);
}

如果权限点过细,也要避免走向另一个极端:权限命名混乱、无人维护。建议按资源和动作组织,例如:

  • system:user:read
  • system:user:update
  • system:role:assign
  • audit:log:export

3. 对敏感数据做字段级控制

未授权访问的危害往往来自返回数据过多。即使接口权限修好了,也建议检查返回对象,避免把内部字段直接暴露给前端。

例如用户详情接口不应返回:

  • 密码哈希
  • 盐值
  • 重置密码 token
  • 内部备注
  • 身份证、手机号等非必要敏感字段

推荐使用专门的 VO/DTO,不要直接返回数据库实体。

public class UserVO {
    private Long id;
    private String username;
    private String displayName;
    private String status;
}

4. 加入自动化安全回归

这类问题很适合做自动化回归。可以在集成测试中覆盖关键接口的无 token、低权限、高权限访问结果。

@Test
void adminApiShouldRejectAnonymousRequest() throws Exception {
    mockMvc.perform(get("/admin/user/list"))
            .andExpect(status().isUnauthorized());
}

对历史项目而言,哪怕先覆盖核心管理接口,也比完全依赖人工测试更可靠。

5. 日志与告警

权限拦截失败不是只返回错误就结束了,建议记录必要日志,便于排查异常访问。日志内容应包含:

  • 请求路径
  • 请求方法
  • 用户 ID 或匿名标识
  • 客户端 IP
  • 失败原因:未登录、token 失效、权限不足

注意不要在日志中记录完整 token、密码、验证码等敏感内容。

注意点

  • 不要把前端菜单隐藏当作权限控制。
  • 不要长期保留临时白名单。
  • 不要在多个地方重复实现互不一致的鉴权逻辑。
  • 不要让测试环境安全配置直接进入生产。
  • 不要默认信任内网请求,尤其是容器化和微服务环境。
  • 不要把实体对象直接作为接口返回值。

一个较实用的排查清单

检查项建议
接口是否默认需要登录除明确公开接口外全部认证
后台接口是否有权限点管理类接口增加方法级授权
白名单是否过宽避免使用大范围通配符
网关是否是唯一入口确认后端服务未直接暴露
返回字段是否过多使用 DTO/VO 控制输出
是否有自动化测试覆盖匿名、低权、高权访问
是否有审计日志记录失败原因但避免敏感信息

总结

后台未授权访问通常不是某个接口少写一行代码这么简单,而是权限体系缺少统一边界的表现。修复时不要只补单个接口,更应回到整体设计:默认拒绝、统一认证、细粒度授权、最小化返回字段、自动化回归。

从代码审计角度看,排查这类问题最有效的方法是把接口清单、鉴权配置、权限注解和实际访问结果放在一起对照。只看页面很容易漏,只看代码也可能忽略运行时配置。对于长期维护的业务系统,这套检查方式值得沉淀到发布流程中。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…

SiteMap HACKHAT © 2026 All rights reserved.