【问题标题】:Springboot testing with keycloak使用 keycloak 进行 Springboot 测试
【发布时间】:2020-01-17 12:19:20
【问题描述】:

我正在尝试运行简单的单元测试,Keycloak 配置正确(我对其进行了测试,我的 mvc 应用程序正在连接并且用户已通过身份验证_但现在我尝试测试我的控制器,即使 我使用了 spring slices keycloak 适配器被调用并给了我错误。适配器配置主要来自 keycloak 文档

@WebMvcTest(UserController.class)
class UserControllerTest {



    @MockBean
    UserService userService;

    @Autowired
    MockMvc mockMvc;

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
        reset(userService);
    }

    @Test
    void logout() throws Exception {
        mockMvc.perform(get("/logout"))
                .andExpect(status().isOk());
    }
}

但是当我尝试运行它时出现错误,堆栈跟踪:

java.lang.NullPointerException
    at org.keycloak.adapters.KeycloakDeploymentBuilder.internalBuild(KeycloakDeploymentBuilder.java:57)
    at org.keycloak.adapters.KeycloakDeploymentBuilder.build(KeycloakDeploymentBuilder.java:205)
    at org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver.resolve(KeycloakSpringBootConfigResolver.java:37)
    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:81)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    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:118)
    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:133)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:182)
    at org.czekalski.userkeycloak.controller.UserControllerTest.logout(UserControllerTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

添加到测试@TestPropertySource("classpath:secTest.properties") where inside secTest.properties keycloak.enabled = false 没有帮助

待测代码:

@Controller
public class UserController {

    private final UserService userService;

    public UserController( UserService userService) {

        this.userService = userService;
    }



    @GetMapping("/index")
    public String logout(){
        return "users/logout";
    }


    @GetMapping("/logged")
    public String loggedIn(Model model){
        model.addAttribute("token",   userService.getloggedInUser());
        return "users/logged";
    }

【问题讨论】:

  • 能否提供测试范围的Spring配置?
  • 你这是什么意思?
  • 您找到解决方案了吗?我也有同样的问题。

标签: spring spring-boot keycloak


【解决方案1】:

我刚刚写了一组库to ease unit-testing of secured Spring apps

它包括一个@WithMockKeycloackAuth 注释,以及Keycloak 专用的MockMvc 请求后处理器和WebTestClient 配置器/修改器

示例用法:

@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
@ContextConfiguration(classes = GreetingApp.class)
@ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTests extends ServletUnitTestingSupport {
    @MockBean
    MessageService messageService;

    @Test
    @WithMockKeycloackAuth
    public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretRouteIsNotAccessible() throws Exception {
        mockMvc().get("/secured-route").andExpect(status().isForbidden());
    }

    @Test
    @WithMockKeycloackAuth("AUTHORIZED_PERSONNEL")
    public void whenUserIsGrantedWithAuthorizedPersonelThenSecretRouteIsAccessible() throws Exception {
        mockMvc().get("/secured-route").andExpect(content().string(is("secret route")));
    }

    @Test
    @WithMockKeycloakAuth(
            authorities = { "USER", "AUTHORIZED_PERSONNEL" },
            id = @IdTokenClaims(sub = "42"),
            oidc = @OidcStandardClaims(
                    email = "ch4mp@c4-soft.com",
                    emailVerified = true,
                    nickName = "Tonton-Pirate",
                    preferredUsername = "ch4mpy"),
            privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))
    public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
        mockMvc().get("/greet")
                .andExpect(status().isOk())
                .andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
                .andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
                .andExpect(content().string(containsString("USER")));

根据我向您建议的工具数量,您可能会从 maven-central 获得 spring-security-oauth2-test-addonsspring-security-oauth2-test-webmvc-addons

<dependency>
  <groupId>com.c4-soft.springaddons</groupId>
  <artifactId>spring-security-oauth2-test-addons</artifactId>
  <version>2.3.4</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.c4-soft.springaddons</groupId>
  <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
  <version>2.3.4</version>
  <scope>test</scope>
</dependency>

如果您只对@WithMockKeycloakAuth 注释感兴趣,那么首先就足够了。其次添加了流畅的 API(MockMvc 请求后处理器)和其他东西,如 MockMvc 包装器,具有默认值的内容类型和接受标头

【讨论】:

  • 好吧,干得好!那时我以不同的方式解决了它。解决方案发布在单独的线程中。我还必须测试您的解决方案:)
  • 在版本2.3.4 中没有注释WithMockKeycloakAuth 而是WithMockAuthentication。还有一点,我也没有找到mockMvc()这个方法。有什么建议吗?
  • @WithMockKeycloakAuth 在 2.3.4 中有:github.com/ch4mpy/spring-addons/blob/master/…
  • maven 依赖 sn-p 中有错字。对不起。我的库仅支持带有 MockMvc 的 KeycloakAuthenticationToken(不是 WebTestClient),因此应该将 spring-security-oauth2-test-webmvc-addons 而不是 spring-security-oauth2-test-webflux-addons 作为测试依赖项拉
  • 最后的建议,请阅读自述文件,特别是关于在本地克隆 repo 以实验测试样本的部分。您将节省大量时间来配置自己的测试。
【解决方案2】:

我也找到了一种方法来做到这一点,但它是相当丑陋的方式。您可以出于测试目的关闭 keycloak。可以更好吗?

在属性文件中(我的是app-dev.properties)设置:

keycloak.enabled = false

在我设置的应用程序安全配置类中

@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
public class KeycloakConfiguration extends KeycloakWebSecurityConfigurerAdapter {

我还使用安全配置创建了单独的类,但仅用于使用这些注释进行测试

@Profile("app-dev.properties")
@Configuration
@EnableWebSecurity
public class TestSecConfig extends WebSecurityConfigurerAdapter{}

在控制器的集成测试中

@ActiveProfiles("app-dev.properties")
@WebMvcTest(value = FunController.class)
@Import(TestSecConfig.class)
@TestPropertySource("classpath:app-dev.properties")
class FunControllerIT{} 

来源:

解决方法https://github.com/spring-projects/spring-boot/issues/6514

【讨论】:

    【解决方案3】:

    您还可以使用 @AutoConfigureMockMvc(addFilters = false) 注释您的 Testclass 以禁用应用程序上下文中的过滤器。

    【讨论】:

      【解决方案4】:

      测试期间 Keycloak + Spring Security 设置的解决方案很棘手,但恕我直言,以下只是测试正确设置环境的正确解决方案。首先是大腿,我们不想有条件地使用安全配置,因为我们也想测试它(例如,RolesAllowed、Post 和 Pre 注释)。出于同样的原因,我们也不想为测试创建特殊配置。出路是如下配置:

      @Configuration #mandatory
      @EnableWebSecurity #mandatory
      @EnableGlobalMethodSecurity(jsr250Enabled = true) #conditional
      @EnableConfigurationProperties(KeycloakSpringBootProperties.class) #mandatory
      @Slf4j #conditional
      class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
      
          @Override
          protected void configure(@NotNull HttpSecurity http) throws Exception {
              super.configure(http);
              ...
          }
      
          @Bean
          public @NotNull KeycloakConfigResolver keycloakConfigResolver() {
              return new KeycloakSpringBootConfigResolver();
          }
      
      }
      

      真正重要的是@EnableConfigurationProperties(KeycloakSpringBootProperties.class) 的存在。没有它,您将在测试期间获得 NPE。在测试资源中的 application.yml 或 application-test.yml(属性配置也类似)添加以下内容:

      keycloak:
        enabled: false #Keycloak is not needed in full functionality
        realm: mock #There is no configuration mock for Keycloak in case of testing. Realm must be set but it is not used
        resource: mock #There is no configuration mock for Keycloak in case of testing. Resource must be set but it is not used
        auth-server-url: http://mock #There is no configuration mock for Keycloak in case of testing. URL must be set but it is not used
        bearer-only: true # Because Keycloak do redirect in case of unauthenticated user which leads to 302 status, we switch to strict Bearer mode
        credentials:
          secret: mock
      

      使用此设置和@WithMockUser 注释,您的@WebMvcTest 将在与生产环境相同的安全配置中运行而不会出现错误。

      【讨论】:

      • 在尝试了很多事情之后,它工作正常!
      猜你喜欢
      • 2020-09-23
      • 2017-02-05
      • 1970-01-01
      • 2018-08-16
      • 1970-01-01
      • 1970-01-01
      • 2018-10-24
      • 2021-05-02
      • 1970-01-01
      相关资源
      最近更新 更多