Debug Past an HTTP 403 Without Breaking Spring Security

TL;DR · AI 摘要
IntelliJ IDEA 的安全内联功能允许开发者在调试 Spring Security 时临时解锁端点,无需修改配置或重启应用。
核心要点
- 安全内联可实时显示端点所需角色,避免临时修改 SecurityConfig
- Unlock 功能影响所有访问该 URI 的请求,需谨慎使用
- 配置链接直接跳转到安全规则代码,提升调试效率
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- IntelliJ IDEA 安全调试
- 安全内联功能
- 角色显示
- 配置跳转
- 解锁端点
- 临时绕过验证
- 全网影响警告
金句 / Highlights
值得收藏与分享的关键句。
安全内联显示端点所需角色,无需修改 SecurityConfig
Unlock 功能影响所有访问该 URI 的请求,需谨慎使用
配置链接直接跳转到安全规则代码,提升调试效率
不破坏 Spring Security 的情况下调试 HTTP 403 - JetBrains 博客
IntelliJ IDEA
IntelliJ IDEA – Java 和 Kotlin 专业开发的首选 IDE
关注
- 关注:
- LinkedIn LinkedIn
- Bluesky Bluesky
- X X
- Facebook Facebook
- YouTube YouTube
- RSS RSS
下载
功能
教程
不破坏 Spring Security 的情况下调试 HTTP 403
Andrey Belyaev
您在 Spring MVC 控制器或服务内部设置了断点,向 HTTP 端点发送请求,但没有任何反应。响应返回了 – 是一个 403(或 401,或跳转到登录页面)。
此时,您原本想捕获的 bug 已经不再是唯一的问题。现在,您需要回答不同的问题:
- 该端点需要哪些角色或权限?
- 是否应该暂时禁用安全功能来调试背后的逻辑?
IntelliJ IDEA 的安全内联提示正是为此设计。您无需临时修改 SecurityConfig 并重启应用,或在 HTTP 测试工具中折腾认证配置,即可在 IDE 中直接查看和调整运行时安全设置。
查看端点需求
当安全端点阻止请求时,第一个问题是“当前运行的应用程序对这个端点需要哪些角色?”
内联提示会显示端点基于角色的访问要求 – 也就是大多数 HTTP 授权机制依赖的 hasRole / hasAuthority 匹配器。不过,并非所有安全规则都能以角色列表形式展示。例如,自定义 AuthorizationManager 中以代码定义的规则会显示为未知 – 仅显示一个锁图标,不显示角色。
在真实的 Spring 应用中查看端点的实际安全状态非常重要。授权可能依赖多个 SecurityFilterChain bean、匹配器顺序、激活的 profile 以及条件注册的配置。阅读 SecurityConfig 类并不总是等同于了解运行中的应用程序实际需要什么。
如果角色列表与预期不符,您仍然需要阅读 SecurityConfig 类。因此,内联提示会提供一个链接,指向端点背后真实的权限配置代码。
为当前调试会话解锁端点
当您确定端点需要的角色后,下一个问题是:“应该跳过端点授权,还是用特定权限重新发送请求?”这两个选项都可以在内联提示的解锁操作中完成。
点击解锁前请先阅读以下内容。解锁操作作用于正在运行的进程本身,而不仅仅是来自 IDE 的请求。任何访问该 URI 和方法的客户端都会受到影响:curl、Postman、浏览器等。如果调试的应用在您的网络中可访问,任何对该 URI 和方法的请求都会被视为已通过您授予的权限认证 – 对所有人有效。请谨慎解锁端点,在完成调试后及时重新锁定或重启应用。
由于解锁会影响安全设置,这里需要说明解锁的范围和持续时间:
- 作用范围 – 仅适用于精确的请求 URI 和 HTTP 方法。具体 URI 来自您解锁时正在处理的请求 – 例如 .http 文件、浏览器或 curl 命令中的失败请求。在 IDEA HTTP 客户端中解锁 GET /admin/users/42 仅会解锁该请求,而 POST /admin/users/42 和 GET /admin/users/99 仍会被阻止。在控制器代码中解锁 @GetMapping("/admin/users/{id}") 会为任何 {id} 值解锁该端点。
- 层级 – 仅覆盖 HTTP 接口授权;方法级别的检查(如 @PreAuthorize)会单独评估(见下文“Unlock 未覆盖的范围”)。
- 有效期 – 仅在当前调试会话期间有效,或直到你再次锁定路径。重启应用会清除所有解锁状态。无论是完整的 JVM 重启,还是 Spring Boot DevTools 的进程内重启均可生效。解锁的路径及其权限集会保留在当前会话中,并随会话一同存在。
- 持久化 – 应用中不会保存任何信息。唯一保留的是 IDE 本地信息:每个接口最后一次模拟的用户名,用于在重新解锁时预填充。
因此,根据你的任务需求,你可以通过安全指示符内联功能执行以下操作:
简单解锁
有时你测试的并非安全机制本身。你已经知道接口受到保护,但需要访问其背后的逻辑。解锁功能会将请求视为已认证,并赋予该 URI 和方法所需的所有角色权限。Spring Security 仅看到一个通过验证的身份,你可以继续执行原本的调试任务。这种模式适用于以下真实场景:
- 验证控制器中的请求或映射逻辑;
- 调试受保护路由后端的服务;
- 本地认证配置阻断的烟雾测试;
- 不希望因配置修改和重启而中断的 bug 复现。
如果 IDE 无法评估接口的角色列表,此功能将不可用。对于这种情况,可以使用…
使用自定义权限解锁
内联弹出菜单还允许你指定用户名和自定义角色列表。将此视为测试特定权限集的方式。它使你能够在不创建真实账户、不修改测试数据或不经历完整 SSO 或令牌流程的情况下,验证接口后端基于权限的行为。
例如,你可以在弹出菜单中输入用户名 admin 并指定 ROLE_ADMIN、ROLE_MANAGER 权限。重新发送请求后,可以回答以下问题:
- 当请求具有指定权限时,控制器是否返回预期数据?
- 问题是出在控制器逻辑,还是请求使用的权限集?
- 下游代码(分支逻辑、数据过滤、权限检查)对此权限集的具体行为如何?
- 审计引擎是否会正确记录用户名 admin?
执行上述任一操作时,请求会通过调试器在运行的应用程序中创建的人工 Authentication 对象在 SecurityContext 中运行。读取权限集的代码(通过 hasRole / hasAuthority 检查、SecurityContextHolder.getContext().getAuthentication(),或 Principal / Authentication 方法参数)会看到 IntelliJ IDEA 提供的权限。
需要注意的一点:带有 @AuthenticationPrincipal 注解的控制器参数在内联解锁请求中无法解析 – 它会返回 null。这是一个已知问题,IDEA-389767,目前仍处于开放状态,影响 2026.2 版本 – 请查看最新状态。(见下文“Unlock 的底层实现原理”部分了解原因。)
与常规本地变通方法(如 permitAll 开发环境配置、模拟认证器、注释掉的过滤器)相比,此方法无需重建或重启应用,也不会在配置代码中留下可能意外进入生产环境的残留代码。
Unlock 的底层实现原理
这既不是解锁后门,也不是绕过CSRF保护。实际上,解锁操作不会禁用或暂停任何功能。它会将请求视为已通过授权,使用你授予的权限对特定URI和方法进行验证,并让Spring Security继续后续的评估流程。
如果应用程序在修改状态的请求上强制要求CSRF令牌,那么CsrfFilter会在过滤器链中更早执行,仍然会拒绝缺少或无效令牌的POST/PUT/DELETE请求。因此,你仍然需要有效的令牌,或者需要单独配置CSRF豁免。
解锁功能是调试时的特性。请注意,这是一个内部机制,而非公开的IDE API。
应用启动时,调试器会在Spring Security自身的AuthorizationFilter中设置一个隐藏的非暂停断点——也就是Spring判断请求是否被允许的确切位置。在Spring应用上下文初始化完成后,IDE会读取实际生效的HTTP授权规则。调试器不会解析你的源代码,而是从正在运行的JVM中读取实时的SecurityFilterChain,以此确定每个端点所需的权限。这就是为什么内联显示的是HTTP层的实际权限列表。点击“解锁”按钮会将URL、用户名和角色添加到IDE为调试会话维护的集合中。应用程序代码和安全配置不会被修改。
其余的处理过程对每个请求都会自动执行,不会暂停任何操作。当请求到达“已解锁”的URL时,执行流程会通过AuthorizationFilter中的非暂停断点。如果URL在“已解锁”列表中,调试器会向Spring的SecurityContext中添加一个TestingAuthenticationToken实例。这个认证对象已经被标记为已认证,使用选定的用户名作为主体,以及“解锁”角色作为授权权限。然后请求继续处理。之后授权引擎读取安全上下文,发现已提供的认证主体,会正常进行评估。对于任何未解锁的URL,断点评估会立即返回,正常的安全机制继续生效。
认证对象中使用的主体是普通的用户名字符串,而不是你应用程序中的UserDetails或其他自定义主体类型。这就是为什么在“为当前调试会话解锁端点”章节中提到,使用@AuthenticationPrincipal注解(或任何自定义主体类)的控制器参数无法解析已解锁的请求。
你可以将认证对象注入控制器方法参数中,因为人工创建的认证对象可以通过SecurityContextHolder访问。因此以下代码应该可以正常工作:
@GetMapping("/active")
ResponseEntity<List<Member>> getAllActive(Principal principal) {
log.info("Auth Principal Name: {}", principal.getName());
//...
}请注意这些参数需要声明为TestingAuthenticationToken的基类型:Principal或Authentication,而不是具体的子类型。例如,如果参数类型为JwtAuthenticationToken,请求时会抛出IllegalStateException而不是解析成功,因为Spring MVC会检查实际注入的TestingAuthenticationToken是否确实是声明类型的实例。虽然这比@AuthenticationPrincipal的静默空值更明显,但根本原因都是类型不匹配。依赖具体主体对象类型、令牌子类型、声明、凭证或会话身份的代码,对于已解锁的端点可能仍然表现不同甚至失败。
解锁钩子 AuthorizationFilter、authorizeHttpRequests 后面的过滤器以及 AuthorizationManager API。该 API 自 Spring Security 5.x 版本起可用,在 6.x 版本中非弃用路径仍适用。如果你的配置仍在使用旧版 authorizeRequests/FilterSecurityInterceptor,该过滤器将不会运行,因此解锁功能不适用。
安全内联提示默认处于启用状态,仅在 Spring 调试器激活时生效。你可以在 IntelliJ 的 Registry(通过 Find Action → Registry… 打开)中使用 spring.debugger.security.enabled 标志将其关闭。安全内联提示也支持远程 JVM,包括共享或预发布环境。请注意,如果有人在远程应用中解锁了端点,系统不会生成日志信息或 actuator 指示器。唯一能确认的方式是查看附加 IDE 编辑器中的内联提示。
Unlock 不涵盖的范围
Unlock 适用于 HTTP 端点安全:如 SecurityFilterChain 的 URL 匹配规则、requestMatchers() 方法调用等。它仅作用于 Servlet 栈,断点被添加到 Spring Security 的 AuthorizationFilter 中。这意味着 Spring WebFlux 不被支持。WebFlux 使用的是另一个类 AuthorizationWebFilter,目前 Unlock 尚未对其做钩子处理。
目前还不支持方法级安全注解,例如:
- @PreAuthorize;
- @PostAuthorize;
- @Secured。
这些注解没有对应的内联提示,无法查看其要求条件,也没有直接针对它们的解锁操作。
不过,方法安全拦截器读取的同样是注入的 Authentication 对象。如果服务方法被 @PreAuthorize 保护,而你在解锁端点时注入的权限满足该注解条件,该检查将通过。
来看一个简单示例:
@RestController
class UserController {
@GetMapping("/admin/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findUser(id);
}
}
@Service
class UserService {
@PreAuthorize("hasRole('ADMIN')")
public User findUser(Long id) {
// ...
}
}
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated());
return http.build();
}
}当你以 ROLE_ADMIN 解锁 GET /admin/users/{id} 请求时,从 UserController 调用到 UserService.findUser 的请求也会通过 @PreAuthorize 检查——不是因为 Unlock 直接作用于该注解,而是因为方法安全拦截器读取的是断点已填充的相同 SecurityContext。
一次解锁,多个客户端
请求可能来自多个位置:IntelliJ IDEA 的 .http 文件、curl 脚本、Postman 集合或测试套件调用的本地调试应用。如果请求被拒绝,无需切换到其他 HTTP 测试工具浪费时间。
对于通过内置 IntelliJ IDEA HTTP 客户端发送的请求,安全内联提示会直接显示在 .http 文件的请求 URL 旁边,你甚至无需切换到控制器代码。对于外部客户端,你需要打开控制器代码,解锁端点,然后重新运行相同的 curl 或 Postman 请求。
关于自动化和 AI 工作流的说明
在 IntelliJ IDEA 2026.2 中,解锁功能可通过编辑器中的内联提示或 .http 文件实现,这是一个手动操作,需在 IDE 内执行。目前尚无程序化开关,因此外部脚本或 AI 代理无法独立使用该功能。我们计划在后续版本中推出 MCP 工具及相关技能,使代理能够直接获取角色列表并对选定端点执行锁定/解锁操作,无需人工介入。该功能预计将在 2026.3 版本中上线。
在此之前,您可从 IDE 中手动解锁端点一次,以便代理进行测试。解锁后,该端点在整个调试会话期间将保持解锁状态,代理可反复重放 curl、.http 或测试请求,无需再次操作。该手动步骤只需执行一次,因此代理在迭代下游控制器或服务逻辑时,可在无人干预的情况下持续运行。
结论
安全内联提示的设计旨在简化您的开发工作。它们会显示端点的真实安全规则,并允许您通过规则以更便捷地进行调试。您可查看端点的具体要求,并在需要时解锁端点。随后,请求将被视为已通过您为调试会话选择的权限进行认证。所有操作均无需修改 SecurityConfig 或重启应用即可完成。
通过应用解锁功能,IDEA 会在运行中的应用中临时更改安全设置。它会向所有访问该端点的客户端开放您解锁的精确 URI 和方法。如果在控制器代码中解锁了路径模式,所有匹配该模式的路径都将被开放。
该路径将保持开放状态,直到您再次锁定或调试会话结束。请谨慎使用此功能,尤其是在具有网络访问权限的应用中。
以下是一些需要特别说明的限制:
- 仅限 Servlet – 内联提示和解锁功能通过 Spring Security 的
AuthorizationFilter实现。该过滤器仅存在于 Servlet 栈中,因此目前尚不支持 WebFlux。 - 仅限调试模式 – 此功能需要处于活动的调试会话中,无论是本地还是远程 JVM。
- Principal/Authentication,而非具体子类型 – 将控制器方法参数声明为
Principal或Authentication。标注了@AuthenticationPrincipal的参数会返回null。若参数类型为具体子类型(如JwtAuthenticationToken),则会抛出错误,因为 Spring MVC 会检查注入对象是否与声明类型匹配。
内联提示是 IntelliJ IDEA Ultimate 的 Spring Debugger 插件的一部分,自 2026.2 版本起提供。
Debug Spring Spring Debugger
- Share
Prev post Java Annotated Monthly – September 2026 Java 27 in IntelliJ IDEA
Next post