SecurityContextHolder returns 错误的用户名
SecurityContextHolder returns the Wrong Username
在使用 MockMVC 测试 REST 端点时,Spring SecurityContextHolder 偶尔会在同一测试中 returns 错误的用户名。我有一个服务,其中有一个方法 returns 用户名和 JPA 存储库检查用户是否存在(释义):
String username = SecurityContextHolder.getContext().getAuthentication().getName();
Optional<RemoteUser> foundUser = userRepository.findOneByUsername(username);
测试在必要时用 @WithMockUser(username = "..." roles="...")
注释。之后每个测试都被拆除,Spring 应用程序上下文被刷新。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = IntegrationTestApplication.class)
public abstract class IntegrationTest {
...
@Resource
private WebApplicationContext applicationContext;
protected MockMvc mockMvc;
...
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
}
...
}
大约 97-98% 的时间,测试 运行 都很好。但是,有时,身份验证对象返回的用户名不是 @WithMockUser
注释中定义的用户名。使用日志语句,我什至看到在其他测试中使用的用户名 类 已经 运行。每次测试前都会设置数据库用户,因此如果返回的用户名不在数据库中,则测试会失败,具体取决于需要用户的情况。
更奇怪的是,这个服务中的这个方法在测试过程中可以调用多次,有时用户名是对的,然后突然就错了。我不明白这怎么可能。我的理解是 SecurityContextHolder bean 是线程安全的,因此测试的脆弱性让我感到困惑。怎么会这样?
还有几点需要注意:
- 使用 Spring 启动 2.2.7
- 测试不是 运行 并行的。
- 上述服务总是通过字段注入延迟注入 (
@Lazy
)。我不知道这是否是一个重要的细节,但这不是我的代码,这是唯一使用此注释的服务,我不知道为什么要这样做。
- 有一些方法用
@Async
注释,我认为可能会有副作用,但我没有任何证据支持这种想法,这种想法纯属胡扯。
- 我调试了TestSecurityContextHolder,确实调用了清除上下文的方法。
- 我也看到了这个SecurityContextHolder gives wrong User details,但是第一,问题没有答案,第二,我的测试用例中没有并发请求。
在进一步研究了 SecurityContextHolder bean 之后,我找到了有关线程可用的不同策略的更多信息。我发现了一个设置这个的 bean:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
这会将安全上下文传播到从主线程派生的所有线程。它应该被排除在测试上下文之外,但事实并非如此。从测试应用程序上下文中删除此 bean 后,之前只是间歇性失败的一个测试用例现在每次都失败。
长话短说,利用 SecurityContextHolder 对象的服务方法在 并行流 中被多次调用。这把我带到了这里:。将其重构为仅在流外调用一次似乎已经解决了这个问题。
在使用 MockMVC 测试 REST 端点时,Spring SecurityContextHolder 偶尔会在同一测试中 returns 错误的用户名。我有一个服务,其中有一个方法 returns 用户名和 JPA 存储库检查用户是否存在(释义):
String username = SecurityContextHolder.getContext().getAuthentication().getName();
Optional<RemoteUser> foundUser = userRepository.findOneByUsername(username);
测试在必要时用 @WithMockUser(username = "..." roles="...")
注释。之后每个测试都被拆除,Spring 应用程序上下文被刷新。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = IntegrationTestApplication.class)
public abstract class IntegrationTest {
...
@Resource
private WebApplicationContext applicationContext;
protected MockMvc mockMvc;
...
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
}
...
}
大约 97-98% 的时间,测试 运行 都很好。但是,有时,身份验证对象返回的用户名不是 @WithMockUser
注释中定义的用户名。使用日志语句,我什至看到在其他测试中使用的用户名 类 已经 运行。每次测试前都会设置数据库用户,因此如果返回的用户名不在数据库中,则测试会失败,具体取决于需要用户的情况。
更奇怪的是,这个服务中的这个方法在测试过程中可以调用多次,有时用户名是对的,然后突然就错了。我不明白这怎么可能。我的理解是 SecurityContextHolder bean 是线程安全的,因此测试的脆弱性让我感到困惑。怎么会这样?
还有几点需要注意:
- 使用 Spring 启动 2.2.7
- 测试不是 运行 并行的。
- 上述服务总是通过字段注入延迟注入 (
@Lazy
)。我不知道这是否是一个重要的细节,但这不是我的代码,这是唯一使用此注释的服务,我不知道为什么要这样做。 - 有一些方法用
@Async
注释,我认为可能会有副作用,但我没有任何证据支持这种想法,这种想法纯属胡扯。 - 我调试了TestSecurityContextHolder,确实调用了清除上下文的方法。
- 我也看到了这个SecurityContextHolder gives wrong User details,但是第一,问题没有答案,第二,我的测试用例中没有并发请求。
在进一步研究了 SecurityContextHolder bean 之后,我找到了有关线程可用的不同策略的更多信息。我发现了一个设置这个的 bean:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
这会将安全上下文传播到从主线程派生的所有线程。它应该被排除在测试上下文之外,但事实并非如此。从测试应用程序上下文中删除此 bean 后,之前只是间歇性失败的一个测试用例现在每次都失败。
长话短说,利用 SecurityContextHolder 对象的服务方法在 并行流 中被多次调用。这把我带到了这里: