在不影响Spring Security安全机制的前提下调试HTTP 403错误
你在 Spring MVC 控制器或服务中设置了断点,向 HTTP 端点发送请求,却什么也没有发生。响应倒是回来了——却是 403(或者 401,又或者重定向到登录页)。
在那一刻,你想调试的 bug 已经不再是唯一的问题。现在,你需要回答不同的问题:
- 这个端点要求哪些角色或权限?
- 也许我应该临时禁用安全性,然后调试它背后的逻辑?
IntelliJ IDEA 中的安全内嵌提示就是为此而设计的。你不需要临时修改 SecurityConfig 并重启应用,也不需要在 HTTP 测试工具里摆弄认证,而是可以直接在 IDE 内部检查和调整运行时安全设置。
查看端点需要什么
当受保护的端点阻止某个请求时,第一个问题就是:“当前运行中的应用对这个端点到底要求哪些角色?”
内嵌提示显示端点的基于角色要求——即大多数 HTTP 授权所依赖的 hasRole / hasAuthority 形式的匹配器。但是,并非所有安全规则都能用角色列表表示。例如,在自定义 AuthorizationManager 中通过代码定义的规则会显示为未知——一个单独的锁图标,没有角色。
[LOADING...]
在实际的 Spring 应用中,查看端点的真实安全状态很重要。授权可能依赖多个 SecurityFilterChain bean、匹配器顺序、激活的 profile 和按条件注册的配置。阅读你能找到的 SecurityConfig 类,并不一定等于知道正在运行的应用的要求。
如果角色列表与你的预期不符,你仍然需要阅读 SecurityConfig 类。因此,内嵌提示提供了指向端点背后实际安全配置代码的配置链接。
为当前调试会话解锁端点
当你弄清楚端点需要哪些角色后,下一个问题是:“我该跳过端点授权,还是用一组特定权限重新发送请求?”在内嵌提示弹窗的解锁操作中,两种选择都可用。
[LOADING...]
点击 Unlock 前请先阅读。 Unlock 作用于正在运行的进程本身,而不仅仅是 IDE 发出的请求。任何访问相同 URI 和方法的客户端都会受到影响:curl、Postman、浏览器等。如果被调试的应用在你的网络上可访问,那么任何针对该 URI 和方法的请求,都会被当作携带你所授予权限的已认证请求——对任何人都是如此。请谨慎解锁端点,并在完成后重新锁定或重启。
由于解锁是影响安全的操作,有必要说明它会打开什么、持续多久:
- 作用范围——仅应用于确切的请求 URI 和 HTTP 方法。具体 URI 来自你在解锁时正在处理的请求——也就是
.http文件、浏览器或 curl 命令中失败的请求。在 IDEA HTTP Client 中解锁GET /admin/users/42不会解锁POST /admin/users/42和GET /admin/users/99。在控制器代码中解锁@GetMapping("/admin/users/{id}")会解锁任意{id}值对应的端点。 - 所在层级——仅覆盖 HTTP 端点授权;
@PreAuthorize之类的方法级检查会单独求值(参见下面的“解锁不覆盖什么”)。 - 有效期——仅持续到当前调试会话结束,或者直到你再次锁定该路径。重启应用会清除所有解锁。完整的 JVM 重启和 Spring Boot DevTools 进程内重启都有效。已解锁的路径及其权限集合属于该会话并随其结束。
- 持久性——应用内不会保留任何内容。唯一被保留的是 IDE 本地信息:你上一次在某个端点上模拟的用户名,以便重新解锁时预填。
因此,根据你的任务,你可以在安全指示器内嵌提示中执行以下操作:
简单解锁
有时,安全并不是你正在测试的内容。你已经知道端点受保护,但需要到达其背后的逻辑。解锁会将这个 URI 和方法的请求视为已认证,并携带该端点所需的全部角色。Spring Security 只会看到一个通过验证的身份,然后你就可以继续原先的调试任务。在以下情况下,这正合适:
- 验证控制器中的请求或映射逻辑;
- 调试受保护路由背后的服务;
- 被本地认证配置阻止的冒烟测试;
- 你不想通过修改配置和重启来打断的 bug 复现。
如果 IDE 无法评估该端点的角色列表,此功能不可用。对于这种情况,请使用:
使用自定义权限解锁
内嵌提示弹窗还允许你指定用户名和自定义角色列表。可以把它看作测试一组特定权限的方式。它让你无需创建真实账户、修改测试数据,也无需走完整的 SSO 或 token 流程,就能检查端点背后基于权限的行为。
[LOADING...]
例如,你可以在弹窗中进入用户名 admin,并配置 ROLE_ADMIN、ROLE_MANAGER 权限。重新发送请求,然后回答这类问题:
- 当请求具有指定权限时,控制器是否返回预期数据?
- 问题出在控制器逻辑中,还是请求所使用的权限集合?
- 下游代码——分支、数据过滤、权限检查——在这个确切权限集合下表现如何?
- 审计引擎是否会记录正确的用户名——
admin?
当你执行其中任何一种操作时,请求会携带一个由调试器在运行中的应用内创建的人工 Authentication 并放入 SecurityContext,其中包含你选择的用户名和角色。任何读取权限集合的代码——通过 hasRole / hasAuthority 检查、SecurityContextHolder.getContext().getAuthentication() 或 Principal / Authentication 方法参数——都会看到 IntelliJ IDEA 提供的权限。
有一点需要注意:带有 @AuthenticationPrincipal 注解的控制器参数在内嵌提示解锁的请求中不会解析——它会返回 null。这是一个已知 bug,IDEA-389767,截至撰写本文时它仍处于打开状态并影响 2026.2——请查看该链接获知当前状态。(原因参见下面的“解锁在底层是如何工作的”。)
与常见的本地临时方案相比——例如 permitAll 的开发 profile、模拟认证器、注释掉过滤器——无需重建或重启应用,也不会在配置代码中留下可能意外进入生产环境的改动。
解锁在底层是如何工作的
解锁是一个后门,还是绕过 CSRF?都不是——解锁不会禁用或挂起任何东西。它把该 URI 和方法的请求视为携带你所授予权限的已认证请求,并让 Spring Security 从那里继续评估。
如果你的应用对改变状态的请求强制校验 CSRF token,CsrfFilter 会在更早的位置执行,仍然会拒绝缺少或无效 token 的 POST / PUT / DELETE 请求。因此,你仍然需要有效的 token,或一个可绕过 CSRF 的单独配置。
解锁是调试期功能。请注意,它是一种内部机制,不是公开的 IDE API。
在应用启动时,调试器会在 Spring Security 自身的 AuthorizationFilter 处放置一个隐藏且不挂起的断点——这正是 Spring 决定请求是否被允许的位置。Spring 应用上下文初始化后,IDE 会读取有效的 HTTP 授权规则。调试器不会解析你的源代码,而是从正在运行的 JVM 中读取实时的 SecurityFilterChain,以确定每个端点要求什么。因此,内嵌提示显示的是 HTTP 层实际的角色列表。单击 Unlock 会将 URL、用户名和角色添加到 IDE 为调试会话保留的集合中。应用代码和安全配置不会被修改。
其余工作在每次请求时完成,不会暂停任何内容。当请求命中“已解锁”的 URL 时,执行流程会经过 AuthorizationFilter 中的非挂起断点。如果该 URL 在“已解锁”列表中,调试器会向 Spring 的 SecurityContext 添加一个 TestingAuthenticationToken 实例。这个认证对象已经标记为已认证,principal 是你选择的用户名,granted authorities 是“解锁”角色。然后请求继续执行。之后,授权引擎读取安全上下文,看到提供的已认证 principal,并正常评估它。对于任何你没有解锁的 URL,断点求值会立即返回,正常的安全逻辑继续生效。
认证对象中使用的 principal 是普通的用户名字符串,而不是应用的 UserDetails 或其它自定义 principal 类型。这就是为什么带有 @AuthenticationPrincipal(或任何自定义 principal 类)注解的控制器参数无法在内嵌提示解锁的请求中解析,如“为当前调试会话解锁端点”一章所述。
由于这个人工认证对象可以通过 SecurityContextHolder 获取,你可以把它注入为控制器方法参数。因此,下面的代码应该可以正常工作:
请注意,这些参数需要声明为 TestingAuthenticationToken 的基本类型:Principal 或 Authentication,而不是具体的子类型。例如,参数类型为 JwtAuthenticationToken 会在请求时抛出 IllegalStateException 而不是解析成功,因为 Spring MVC 会检查真实的 principal——即注入的 TestingAuthenticationToken——是否真的是所声明类型的实例。这比 @AuthenticationPrincipal 静默返回 null 更明显,但根本原因是同样的类型不匹配。依赖于具体 principal 对象类型、token 子类型、claims、credentials 或基于会话身份的代码,可能仍然行为不同,甚至在解锁端点上失败。
Unlock 钩住的是 AuthorizationFilter,也就是 authorizeHttpRequests 和 AuthorizationManager API 背后的过滤器。这个 API 自 Spring Security 5.x 起可用,也是 6.x 中未被弃用的路径。如果你的配置仍使用较旧的 authorizeRequests / FilterSecurityInterceptor,该过滤器不会运行,因此解锁不生效。
安全内嵌提示默认开启,并且仅在 Spring Debugger 激活时工作。你可以通过 IntelliJ 的 Registry(用 Find Action → Registry... 打开)中的 spring.debugger.security.enabled 标志关闭它们。安全内嵌提示也适用于远程 JVM,包括共享或预发布环境。请注意,如果有人解锁了远程应用上的端点,不会产生任何日志消息或 actuator 指示器。唯一检查方式是查看已连接 IDE 编辑器中的内嵌提示。
解锁不覆盖什么
解锁适用于 HTTP 端点安全:即 SecurityFilterChain URL 匹配和 requestMatchers() 方法调用之类的规则。它只在 Servlet 技术栈上有效,因为断点被添加到 Spring Security 的 AuthorizationFilter 中。这意味着 Spring WebFlux 不受支持。WebFlux 使用的是另一个类 AuthorizationWebFilter,解锁尚未钩住它。
目前也不支持方法安全注解,例如:
@PreAuthorize@PostAuthorize@Secured
没有针对它们的内嵌提示,无法查看它们要求什么,也没有直接针对它们的解锁操作。
然而,方法安全拦截器读取的也是同一个注入的 Authentication。如果某个服务方法受 @PreAuthorize 保护,并且你在已解锁端点上注入的权限满足要求,该检查也会通过。
来看一个小示例:
当你以 ROLE_ADMIN 解锁 GET /admin/users/{id} 后,从 UserController 到 UserService.findUser 的调用也会通过 @PreAuthorize 检查——这不是因为解锁直接针对该方法,而是因为方法安全拦截器读取的 SecurityContext 就是断点已经填充好的那个。
一次解锁,多个客户端
请求可能来自多个地方:IntelliJ IDEA 的 .http 文件、curl 脚本、Postman 集合,或者命中本地调试应用的测试套件。如果请求被拒绝,不要浪费时间切换到另一个 HTTP 测试工具。
[LOADING...]
对于从 IntelliJ IDEA 内置 HTTP Client 发送的请求,安全内嵌提示就在 .http 文件中请求 URL 附近,因此你甚至不需要切换到控制器代码。对于外部客户端,你需要打开控制器代码、解锁端点,然后重新运行相同的 curl 或 Postman 请求。
关于自动化与 AI 工作流的说明
在 IntelliJ IDEA 2026.2 中,解锁可通过编辑器或 .http 文件中的内嵌提示执行——这是一个手动的 IDE 内操作。目前还没有可编程的开关,因此外部脚本或 AI agent 无法自行使用它。我们正在规划一个 MCP 工具和相关的 skill,让 agent 能够直接获取角色列表,并对所选端点执行锁定/解锁操作,而无需人工介入。这将面向下一个版本 2026.3。
在那之前,你可以先从 IDE 解锁端点,让 agent 进行测试。在调试会话的剩余时间内,该端点会保持解锁,因此 agent 可以不断重放 curl、.http 或测试请求,无需更多点击。这个手动步骤是一次性设置,之后 agent 在迭代下游控制器或服务逻辑时可以无人值守地运行。
结论
安全内嵌提示旨在简化你(开发者)的工作。它们显示端点的真实安全规则,并让你得以通过它们,从而更轻松地调试。你可以查看端点要求什么,并在需要时解锁。之后,请求在调试会话中会被视为已认证,并使用你选择的权限。所有这些都无需修改 SecurityConfig 或重启应用。
通过执行解锁,IDEA 会在运行中的应用里做出临时安全变更。它会把你解锁的确切 URI 和方法开放给所有能访问到的客户端。如果你在控制器代码中解锁了路径模式,它会开放所有匹配该模式的路径。
该路径会一直保持开放,直到你再次锁定或调试会话结束。请谨慎使用,尤其是当应用可被网络访问时。
有几个限制值得一提:
- 仅限 Servlet——内嵌提示和 Unlock 钩住的是 Spring Security 的
AuthorizationFilter。该过滤器只存在于 Servlet 技术栈中,因此尚不支持 WebFlux。 - 仅限调试模式——该功能需要激活的调试器会话,无论是本地 JVM 还是远程 JVM。
Principal/Authentication,而非具体子类型——将控制器方法参数声明为Principal或Authentication。带有@AuthenticationPrincipal注解的参数会返回null。参数类型为具体子类型(如JwtAuthenticationToken)时则会抛出错误,因为 Spring MVC 会检查注入的对象是否符合声明的类型。
这些内嵌提示是 IntelliJ IDEA Ultimate 的 Spring Debugger 插件的一部分,自 2026.2 版本起提供。