在 WebMvcTest 中为 Keycloak 加载自定义 SecurityConfig 时的 NPE

Posted

技术标签:

【中文标题】在 WebMvcTest 中为 Keycloak 加载自定义 SecurityConfig 时的 NPE【英文标题】:NPE when loading custom SecurityConfig for Keycloak in WebMvcTest 【发布时间】:2020-07-28 09:15:03 【问题描述】:

在 webapp 本身中,一切运行都没有任何问题。身份验证按预期工作。但是,由于我的类路径上有 Spring Security,我的所有控制器测试现在都失败了,因为正在加载默认的 Spring Security 配置。显然,我希望加载我的 SecurityConfig,因为例如我配置了 csrf 已禁用,并且默认情况下每个端点都可以在没有身份验证的情况下访问。 但是当我想加载我的 SecurityConfig 时,我在运行我的测试时得到了一个 NPE。

我通过 org.keycloak:keycloak-spring-boot-starterorg.keycloak.bom:keycloak-adapter-bom:9.0.2 在 Spring Boot 应用程序中使用 Keycloak 适配器。我的 Spring Boot 版本是2.2.6.RELEASE

运行测试时的堆栈跟踪

java.lang.NullPointerException
    at org.keycloak.adapters.KeycloakDeploymentBuilder.internalBuild(KeycloakDeploymentBuilder.java:57)
    at org.keycloak.adapters.KeycloakDeploymentBuilder.build(KeycloakDeploymentBuilder.java:202)
    at org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver.resolve(KeycloakSpringBootConfigResolver.java:39)
    at org.keycloak.adapters.springsecurity.config.KeycloakSpringConfigResolverWrapper.resolve(KeycloakSpringConfigResolverWrapper.java:40)
    at org.keycloak.adapters.AdapterDeploymentContext.resolveDeployment(AdapterDeploymentContext.java:89)
    at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:82)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
    at net.my.domain.exampledomain.controller.ExampleControllerTest.givenExampleRouteURIWithAcceptApplicationXml_whenMockMVC_thenVerifyResponse(ExampleControllerTest.java:43)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

安全配置

@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter 

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) 
    KeycloakAuthenticationProvider keycloakAuthenticationProvider =
        keycloakAuthenticationProvider();
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
    auth.authenticationProvider(keycloakAuthenticationProvider);
  

  @Bean
  public KeycloakSpringBootConfigResolver KeycloakConfigResolver() 
    return new KeycloakSpringBootConfigResolver();
  

  @Bean
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() 
    return new NullAuthenticatedSessionStrategy();
  

  @Override
  protected void configure(HttpSecurity http) throws Exception 
    super.configure(http);
    http.csrf().disable().authorizeRequests().anyRequest().permitAll();
  

ExampleControllerTest

@RunWith(SpringRunner.class)
@WebMvcTest(ExampleController.class)
@ContextConfiguration(classes = ExampleController.class, SecurityConfig.class)
public class ExampleControllerTest 

  @Autowired private MockMvc mockMvc;

  @Test
  public void givenExampleRouteURIWithAcceptApplicationXml_whenMockMVC_thenVerifyResponse()
          throws Exception 
    this.mockMvc
            .perform(MockMvcRequestBuilders.get("/exampleroute").accept(MediaType.APPLICATION_XML))
            .andExpect(status().isOk());
  

【问题讨论】:

如果用 SpringBootTest 替换 WebMvcTest 并删除 ContextConfiguration 会怎样。那它行得通吗?顺便提一句。你可以删除 RunWith 我现在在 ExampleControllerTest 上有以下注释:@SpringBootTest(classes = WebApplication.class) @AutoConfigureTestDatabase @AutoConfigureMockMvc。我得到完全相同的堆栈跟踪。 我不知道为什么,但是当我从 9.0.0 迁移到 9.0.2(或更高版本)时,这似乎中断了。我在他们的论坛上问过同样的问题:keycloak.discourse.group/t/breaking-change-in-9-0-1-9-0-2/3656 【参考方案1】:

这与 9.0.1 不同(虽然我猜你只会从 9.0.2 开始看到它,因为来自 JBoss 存储库和 Maven Central 存储库的 9.0.1 是 missing)。这将在 11.0 (KEYCLOAK-14520) 中修复。同时,您可以通过

    删除 9.0.0 所需的 KeycloakSpringBootConfigResolver bean 添加 Jira 中提到的解决方法
@Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver 
    private KeycloakDeployment keycloakDeployment;
    private AdapterConfig adapterConfig;

    @Autowired
    public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) 
        this.adapterConfig = adapterConfig;
    

    @Override
    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) 
        if (keycloakDeployment != null) 
            return keycloakDeployment;
        
        keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
        return keycloakDeployment;
    

【讨论】:

非常感谢您的回答。这似乎是我正在经历的同一个错误。但是,我没有轻松地应用解决方法。但由于第 11 版已接近尾声,我只能等待那个版本。 你在哪里看到 11 很接近?我没有找到提到的任何特定时间范围。 它在相关票证的评论中被提及。不过,他没有说“接近”实际上是什么意思。这是评论的链接:issues.redhat.com/browse/…

以上是关于在 WebMvcTest 中为 Keycloak 加载自定义 SecurityConfig 时的 NPE的主要内容,如果未能解决你的问题,请参考以下文章

使用 keycloak 进行 Springboot 测试

使用用户 SPI 在 Keycloak 中为外部用户分配角色

我们如何在 Keycloak 授权服务策略中为所有用户分配权限?

是否可以在 keycloak 中为不同组中的用户添加不同的角色?

如何在 Flask-Appbuilder 中为 OAuth2.0 使用自定义提供程序 [keycloak]?

如何在 keycloak 中验证令牌并获取用户详细信息?