Keycloak Mock Server with QuarkusTestResource

1,503 views
Skip to first unread message

Cem Nura

unread,
Oct 9, 2020, 4:27:17 PM10/9/20
to Quarkus Development mailing list
Hello,

I have recently been developing a service that requires openid-connect during testing and thought that a keycloak mock server would simplify the test phase.

I have also supplied a unit test for a quickstart in the past that used TestContainers.

As @loicmathieu has suggested in the PR, WireMock could be used as a keycloak mock server would be better. I thought I would revisit this topic.

I would like to add a module to the test-framework to include a KeycloakTestResource similiar to the H2DatabaseTestResource to be used in both the quickstart unit test and to provide all quarkus developers with a usable keycloak mock server that can be used in there unit test that require openid-connect.

I have made a few attempts;

1- Use a existing keycloak-mock(https://github.com/TNG/keycloak-mock)

Although adding this keycloak-mock project as a dependency and just creating the mock server in a QuarkusTestResourceLifecycleManager is very simple. This dependency uses

vertx_version = '4.0.0-milestone5'

therefore creating a keycloak-mock server directly in the QuarkusTestResourceLifecycleManager leads to a vertx version mismatch since quarkus currently uses vertx 3.9.2 leading to the keycloak-mock not being initiated.

Perhaps if there is a way to use vertx 4.0.0 just for initiating keycloak-mock this solution would be viable.

Attempt Link:


2- Use WireMock

WireMock can be used to form the keycloak mock server. However, when executed the WireMockServer cannot be created since the javax.servlet.api is missing leading to a exception

java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at io.quarkus.test.junit.QuarkusTestExtension.throwBootFailureException(QuarkusTestExtension.java:541)
    at io.quarkus.test.junit.QuarkusTestExtension.beforeEach(QuarkusTestExtension.java:391)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachCallbacks$1(TestMethodTestDescriptor.java:159)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:195)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:195)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachCallbacks(TestMethodTestDescriptor.java:158)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:125)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    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$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    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$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    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.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:188)
    at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:154)
    at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:128)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:428)
    at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
    at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:562)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:548)
Caused by: java.lang.reflect.InvocationTargetException
    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 io.quarkus.test.junit.QuarkusTestExtension.doJavaStart(QuarkusTestExtension.java:227)
    at io.quarkus.test.junit.QuarkusTestExtension.ensureStarted(QuarkusTestExtension.java:519)
    at io.quarkus.test.junit.QuarkusTestExtension.beforeAll(QuarkusTestExtension.java:552)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$8(ClassBasedTestDescriptor.java:368)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:368)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:192)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:78)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:136)
    ... 34 more
Caused by: java.lang.NoClassDefFoundError: javax/servlet/DispatcherType
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at com.github.tomakehurst.wiremock.jetty9.JettyHttpServerFactory.getServerConstructor(JettyHttpServerFactory.java:35)
    at com.github.tomakehurst.wiremock.jetty9.JettyHttpServerFactory.<clinit>(JettyHttpServerFactory.java:30)
    at com.github.tomakehurst.wiremock.core.WireMockConfiguration.<init>(WireMockConfiguration.java:79)
    at com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig(WireMockConfiguration.java:109)
    at com.github.tomakehurst.wiremock.WireMockServer.<init>(WireMockServer.java:119)
    at io.quarkus.test.keycloak.KeycloakTestResource.start(KeycloakTestResource.java:22)
    at io.quarkus.test.common.TestResourceManager.start(TestResourceManager.java:52)
    ... 47 more
Caused by: java.lang.ClassNotFoundException: javax.servlet.DispatcherType
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:419)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:412)
    at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:365)
    ... 56 more

If the same code is moved under a test package the WireMock server has no problem initiating. However, this fails to provide a reusable.

Attempt Link:



I would be happy to work on this feature. What would your recommendations be?

Also, if this feature is found suitible and developed I would happily update the quickstart PR i mentioned.



Kind Regards,
Cem

Sergey Beryozkin

unread,
Oct 9, 2020, 5:06:18 PM10/9/20
to cem....@gmail.com, Quarkus Development mailing list
Hi Cem

Thanks for reminding us about that PR and indeed for these 2 attempts to mock Keycloak Server.
As far as the Quickstart PR is concerned, indeed, as Loic and Pedro indicated that it should apply to the security-openid-connect quickstart. security-openid-connect-web-authentication is already using a TestContainer based approach, so maybe you can just copy that to security-openid-connect one (which uses Bearer tokens) and rework it a little bit (use RestAssured instead of HtmlUnit).

But also, we have an issue to support testing Quarkus OIDC with WireMock, to keep it consistent with the way the oauth2 extension does it, see https://github.com/quarkusio/quarkus/issues/10412

So, I'd like to suggest that you focus your PR to do with mocking Keycloak on resolving #10412, and particularly, using  Wiremock server as it is not tied to Keycloak and as such Quarkus uses will be able to mock other providers like Google etc

Please also note, quarkus-oidc has a public-key property which if set will be used to verify the tokens locally without requiring a remote connection
Thanks Sergey 

--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/e8f3df22-8252-4ac1-a414-851cab9484e1n%40googlegroups.com.

Cem Nura

unread,
Oct 10, 2020, 5:05:00 AM10/10/20
to Quarkus Development mailing list
Thanks for your response Sergey,

I was not aware of the current issue on supporting testing Quarkus OIDC with WireMock. I believe this would make OIDC based services test more straight forward.

I will try to apply the TestContainer approach in security-openid-connect-web-authentication to the security-openid-connect and rework it with RestAssured.

I will also include a WireMockServer approach to resolve mocking Keycloak on issue #10412 on the PR regarding for the security-openid-connect for evaluation.

When setting the public-key property the oidc extension does not retireve the RSA public key from the oidc keycloak server?  So I would have to set the public-key property to simplify the test?


Let me have a crack at this. Again thanks for your kind response.

Sergey Beryozkin

unread,
Oct 11, 2020, 1:46:38 PM10/11/20
to cem....@gmail.com, Quarkus Development mailing list
Hi,

On Sat, Oct 10, 2020 at 10:05 AM Cem Nura <cem....@gmail.com> wrote:
Thanks for your response Sergey,

I was not aware of the current issue on supporting testing Quarkus OIDC with WireMock. I believe this would make OIDC based services test more straight forward.

I will try to apply the TestContainer approach in security-openid-connect-web-authentication to the security-openid-connect and rework it with RestAssured.

I will also include a WireMockServer approach to resolve mocking Keycloak on issue #10412 on the PR regarding for the security-openid-connect for evaluation.

Thanks

When setting the public-key property the oidc extension does not retireve the RSA public key from the oidc keycloak server?  So I would have to set the public-key property to simplify the test?

That is the idea, Clement asked for it awhile back.
It requires setting only 2 properties, clientId can be set to anything:


Let me have a crack at this. Again thanks for your kind response.

Np, thanks for looking into it
Sergey

Cem Nura

unread,
Oct 11, 2020, 4:51:17 PM10/11/20
to Quarkus Development mailing list
Hello,

I have updated my PR to use test containers and moved the unit test to the security-openid-connect project.

I have also included a starting point for a WireMock keycloak server implementation for further discussion.

When using the public-key parameter would there be any use for a running Keycloak Testcontainer? or would only the public-key and two jwt (one for admin and one for alice) be enough? I will have a look at the example you have added. Perhaps there is no need for a Keycloak Testcontainer at all.


Kind Regards,
Cem

Sergey Beryozkin

unread,
Oct 12, 2020, 7:46:22 AM10/12/20
to cem....@gmail.com, Quarkus Development mailing list
Hi Cem

It is brilliant, thanks for showing in this PR both approaches, but as I commented there, lets keep TestContainers only there which is what other OIDC demos are using, but copy and paste `integration-tests/oidc` in the main repo into `integration-tests/oidc-wiremock` and reuse the wiremock code from your PR in this new module, and then later I can update the docs too.

The `public-key` approach is certainly a simpler mechanism to use for testing but is not testing all of quarkus-oidc. Wiremock approach will offer a much better coverage of all the code which interacts with OIDC servers (Keycloak, etc) via Vert.x. The `public-key` approach will also not be adequate if OIDC UserInfo response is required,  if the local JWK store refresh is required or if the tokens have to be (auto) refreshed. And it does not work in the code flow approach - so it is OK for testing simple bearer token verification scenarios but WireMock will offer much more :-)

Cheers, Sergey



Cem Nura

unread,
Oct 12, 2020, 3:52:55 PM10/12/20
to Quarkus Development mailing list
Thanks Sergey

I will update the PR as you said.

Thanks for your comments on  this topic. I would truly like to say that I was influenced by the this project https://github.com/TNG/keycloak-mock. This project works great individually the only problem is that its dependency uses vertx.io:vertx-web:4.0.0-milestone5 as a dependency leading to the server not launching as a QuarkusTestResource since quarkus is using vertx.io:vertx-web:3.9.3. Maybe in the future when (if) quarkus implement vertx 4.0.0.

I have yet another question, considering the WireMock code, what would be a more clean approach then just json strings? Could I use a object and then serialize the object to JSON via Jackson? Perhaps this approach can be cleaner? What would you think?

Again Thanks for your kind response.


Kind Regards,
Cem

Sergey Beryozkin

unread,
Oct 13, 2020, 5:50:42 AM10/13/20
to cem....@gmail.com, Quarkus Development mailing list
Hi Cem,

On Mon, Oct 12, 2020 at 8:53 PM Cem Nura <cem....@gmail.com> wrote:
Thanks Sergey

I will update the PR as you said.

Thanks for your comments on  this topic. I would truly like to say that I was influenced by the this project https://github.com/TNG/keycloak-mock. This project works great individually the only problem is that its dependency uses vertx.io:vertx-web:4.0.0-milestone5 as a dependency leading to the server not launching as a QuarkusTestResource since quarkus is using vertx.io:vertx-web:3.9.3. Maybe in the future when (if) quarkus implement vertx 4.0.0.

Right but it won't be sufficient for Quarkus users testing their services against many other providers which we know are used to protect Quarkus endpoints, with Wiremock we can support, in a fine grain way too if needed, Keycloak, but also other OPs
 
I have yet another question, considering the WireMock code, what would be a more clean approach then just json strings? Could I use a object and then serialize the object to JSON via Jackson? Perhaps this approach can be cleaner? What would you think?

String is fine IMHO
 
Again Thanks for your kind response.

Thanks
Sergey

Cem Nura

unread,
Oct 13, 2020, 4:02:15 PM10/13/20
to Quarkus Development mailing list
On Tuesday, October 13, 2020 at 12:50:42 PM UTC+3 sbia...@redhat.com wrote:
Hi Cem,

On Mon, Oct 12, 2020 at 8:53 PM Cem Nura <cem....@gmail.com> wrote:
Thanks Sergey

I will update the PR as you said.

Thanks for your comments on  this topic. I would truly like to say that I was influenced by the this project https://github.com/TNG/keycloak-mock. This project works great individually the only problem is that its dependency uses vertx.io:vertx-web:4.0.0-milestone5 as a dependency leading to the server not launching as a QuarkusTestResource since quarkus is using vertx.io:vertx-web:3.9.3. Maybe in the future when (if) quarkus implement vertx 4.0.0.

Right but it won't be sufficient for Quarkus users testing their services against many other providers which we know are used to protect Quarkus endpoints, with Wiremock we can support, in a fine grain way too if needed, Keycloak, but also other OPs

Okey I can wrap my head around OIDC service providers other then keycloak.
 
I have yet another question, considering the WireMock code, what would be a more clean approach then just json strings? Could I use a object and then serialize the object to JSON via Jackson? Perhaps this approach can be cleaner? What would you think?

String is fine IMHO
Okey sounds like a plan.

Sergey Beryozkin

unread,
Oct 14, 2020, 9:38:44 AM10/14/20
to cem....@gmail.com, Quarkus Development mailing list
Hi Cem

OK, thanks for completing the quickstart PR.
As discussed earlier, lets try to have an OIDC wiremock integrations tests module added using the code you already had for the quickstart PR. Please also remove a token endpoint from the wiremock OP endpoint, as Quarkus OIDC must not call it as part of the bearer token  authentication flow, instead, consider using either smallrye jwt API to generate a test token:

or if you prefer then use your favourite library. It will help assert that the token endpoint is not called.

I'm already excited about seeing more of your PRs :-), your are interested in improving the security testing experience, so perhaps you can then follow with a similar integration test for the OIDC code flow (we can discuss the details later if it would be of interest, effectively you'd need a few more Wiremock-ed endpoints, the token one this time, plus Authorization one, and we can pick up a simple HtmlUnit test from the existing code flow integration tests),
and then, maybe :-), https://github.com/quarkusio/quarkus/issues/11695 ? Based on how #11695 goes we can do something similar for OIDC too...

Thanks, Sergey





Sébastien Dionne

unread,
Oct 14, 2020, 9:43:13 AM10/14/20
to Quarkus Development mailing list
I want to test it too.  Need to work on Keycloak integration really soon .. thanks for your work

To unsubscribe from this group and stop receiving emails from it, send an email to quark...@googlegroups.com.

Cem Nura

unread,
Oct 14, 2020, 2:23:51 PM10/14/20
to Quarkus Development mailing list
Hi Sergey,

On Wednesday, October 14, 2020 at 4:38:44 PM UTC+3 sbia...@redhat.com wrote:
Hi Cem

OK, thanks for completing the quickstart PR.
As discussed earlier, lets try to have an OIDC wiremock integrations tests module added using the code you already had for the quickstart PR. Please also remove a token endpoint from the wiremock OP endpoint, as Quarkus OIDC must not call it as part of the bearer token  authentication flow, instead, consider using either smallrye jwt API to generate a test token:

Okey I have already started. I will give it a shot and get back to you. I will reference you in the PR.

or if you prefer then use your favourite library. It will help assert that the token endpoint is not called.

I'm already excited about seeing more of your PRs :-), your are interested in improving the security testing experience, so perhaps you can then follow with a similar integration test for the OIDC code flow (we can discuss the details later if it would be of interest, effectively you'd need a few more Wiremock-ed endpoints, the token one this time, plus Authorization one, and we can pick up a simple HtmlUnit test from the existing code flow integration tests),
and then, maybe :-), https://github.com/quarkusio/quarkus/issues/11695 ? Based on how #11695 goes we can do something similar for OIDC too...


I'm glad to hear that and thank you for all your help with the quickstart PR :) I would love to work on OIDC code flow sounds great :)

I didn't know about the @TestSecurity but seems usefull for testing. Which documentation I can read up for the usage on @TestSecurity? Maybe after I have gain a better understanding about @TestSecurity I could work on https://github.com/quarkusio/quarkus/issues/11695 after the WireMock Keycloak and OIDC code flow PR's.

Cem Nura

unread,
Oct 14, 2020, 2:25:04 PM10/14/20
to Quarkus Development mailing list
Thanks Sebastien,

It would be great to get some feedback for sure.

Sergey Beryozkin

unread,
Oct 20, 2020, 7:02:45 AM10/20/20
to cem....@gmail.com, Quarkus Development mailing list
Hi Cem
Thanks for your work around OIDC wiremock integration tests module, this is testing the bearer tokens,
Let me suggest how to add another test to deal with the code flow

I'll ping you there
Cheers, Sergey

Cem Nura

unread,
Oct 20, 2020, 5:19:54 PM10/20/20
to Quarkus Development mailing list
Hello Sergey,

I enjoyed working on the integration test module thanks for your help and comments.

I would gladly give it a shot.
Reply all
Reply to author
Forward
0 new messages