From b28fbb76137875be855f40a51b66699d2423d198 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 25 Sep 2024 16:55:49 +0100 Subject: [PATCH] Use Keycloak devservice for OIDC MTLS authentication --- .../KeycloakDevServicesProcessor.java | 20 +++- integration-tests/oidc/pom.xml | 17 +-- .../src/main/resources/application.properties | 21 ++-- .../src/main/resources/client-keystore.jks | Bin 2214 -> 0 bytes .../src/main/resources/client-keystore.p12 | Bin 0 -> 2712 bytes .../src/main/resources/client-truststore.jks | Bin 937 -> 0 bytes .../src/main/resources/client-truststore.p12 | Bin 0 -> 1254 bytes .../src/main/resources/server-keystore.jks | Bin 2228 -> 0 bytes .../src/main/resources/server-keystore.p12 | Bin 0 -> 2712 bytes .../src/main/resources/server-truststore.jks | Bin 925 -> 0 bytes .../src/main/resources/server-truststore.p12 | Bin 0 -> 1238 bytes .../AbstractBearerTokenAuthorizationTest.java | 12 +- ...BearerTokenAuthorizationInGraalITCase.java | 4 +- ...KeycloakXTestResourceLifecycleManager.java | 110 ++---------------- .../it/keycloak/WebsocketOidcTestCase.java | 8 +- .../keycloak/client/KeycloakTestClient.java | 106 +++++++++++++++-- 16 files changed, 153 insertions(+), 145 deletions(-) delete mode 100644 integration-tests/oidc/src/main/resources/client-keystore.jks create mode 100644 integration-tests/oidc/src/main/resources/client-keystore.p12 delete mode 100644 integration-tests/oidc/src/main/resources/client-truststore.jks create mode 100644 integration-tests/oidc/src/main/resources/client-truststore.p12 delete mode 100644 integration-tests/oidc/src/main/resources/server-keystore.jks create mode 100644 integration-tests/oidc/src/main/resources/server-keystore.p12 delete mode 100644 integration-tests/oidc/src/main/resources/server-truststore.jks create mode 100644 integration-tests/oidc/src/main/resources/server-truststore.p12 diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 1bd7965cbf692..a648b03c1a0dc 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -92,6 +92,7 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_CONTAINER_NAME = "keycloak"; private static final int KEYCLOAK_PORT = 8080; + private static final int KEYCLOAK_HTTPS_PORT = 8443; private static final String KEYCLOAK_LEGACY_IMAGE_VERSION_PART = "-legacy"; @@ -253,8 +254,8 @@ public void run() { return devService.toBuildItem(); } - private String startURL(String host, Integer port, boolean isKeycloakX) { - return "http://" + host + ":" + port + (isKeycloakX ? "" : "/auth"); + private String startURL(String scheme, String host, Integer port, boolean isKeycloakX) { + return scheme + host + ":" + port + (isKeycloakX ? "" : "/auth"); } private Map prepareConfiguration( @@ -383,10 +384,12 @@ private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuild oidcContainer.withEnv(capturedDevServicesConfiguration.containerEnv); oidcContainer.start(); - String internalUrl = startURL(oidcContainer.getHost(), oidcContainer.getPort(), oidcContainer.keycloakX); + String internalUrl = startURL((oidcContainer.isHttps() ? "https://" : "http://"), oidcContainer.getHost(), + oidcContainer.getPort(), oidcContainer.keycloakX); String hostUrl = oidcContainer.useSharedNetwork // we need to use auto-detected host and port, so it works when docker host != localhost - ? startURL(oidcContainer.getSharedNetworkExternalHost(), oidcContainer.getSharedNetworkExternalPort(), + ? startURL("http://", oidcContainer.getSharedNetworkExternalHost(), + oidcContainer.getSharedNetworkExternalPort(), oidcContainer.keycloakX) : null; @@ -518,6 +521,9 @@ protected void configure() { withCommand(startCommand.orElse(KEYCLOAK_QUARKUS_START_CMD) + (useSharedNetwork ? " --hostname-port=" + fixedExposedPort.getAsInt() : "")); addUpConfigResource(); + if (isHttps()) { + addExposedPort(KEYCLOAK_HTTPS_PORT); + } } else { addEnv(KEYCLOAK_WILDFLY_USER_PROP, KEYCLOAK_ADMIN_USER); addEnv(KEYCLOAK_WILDFLY_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD); @@ -639,7 +645,11 @@ public int getPort() { if (fixedExposedPort.isPresent()) { return fixedExposedPort.getAsInt(); } - return getFirstMappedPort(); + return super.getMappedPort(isHttps() ? KEYCLOAK_HTTPS_PORT : KEYCLOAK_PORT); + } + + public boolean isHttps() { + return startCommand.isPresent() && startCommand.get().contains("--https"); } } diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index 8e4c5c8e9dafb..ff0b6fdd56069 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -26,16 +26,10 @@ io.quarkus quarkus-websockets - - org.keycloak - keycloak-core - - - com.sun.activation - jakarta.activation - - + io.quarkus + quarkus-test-keycloak-server + test org.eclipse.angus @@ -46,10 +40,7 @@ quarkus-test-security-oidc test - - org.testcontainers - testcontainers - + io.quarkus quarkus-junit5 diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index cb8865a6636de..d8b1ec529ad7c 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -1,19 +1,22 @@ -# Configuration file -quarkus.oidc.auth-server-url=replaced-by-tests -quarkus.oidc.client-id=quarkus-service-app -quarkus.oidc.credentials.secret=secret +quarkus.keycloak.devservices.create-realm=false +quarkus.keycloak.devservices.start-command=start --https-client-auth=required --hostname-strict=false --https-key-store-file=/etc/server-keystore.p12 --https-trust-store-file=/etc/server-truststore.p12 --https-trust-store-password=password --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json +quarkus.keycloak.devservices.resource-aliases.keystore=server-keystore.p12 +quarkus.keycloak.devservices.resource-aliases.truststore=server-truststore.p12 +quarkus.keycloak.devservices.resource-mappings.keystore=/etc/server-keystore.p12 +quarkus.keycloak.devservices.resource-mappings.truststore=/etc/server-truststore.p12 + quarkus.oidc.token.principal-claim=email quarkus.oidc.tls.verification=required -quarkus.oidc.tls.trust-store-file=client-truststore.jks +quarkus.oidc.tls.trust-store-file=client-truststore.p12 quarkus.oidc.tls.trust-store-password=password -quarkus.oidc.tls.key-store-file=client-keystore.jks +quarkus.oidc.tls.key-store-file=client-keystore.p12 quarkus.oidc.tls.key-store-password=password %tls-registry.quarkus.oidc.tls.tls-configuration-name=oidc-tls -%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=client-keystore.jks +%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=client-keystore.p12 %tls-registry.quarkus.tls.oidc-tls.key-store.jks.password=password -%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=client-truststore.jks +%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=client-truststore.p12 %tls-registry.quarkus.tls.oidc-tls.trust-store.jks.password=password %tls-registry.quarkus.oidc.tls.verification= %tls-registry.quarkus.oidc.tls.trust-store-file= @@ -21,7 +24,7 @@ quarkus.oidc.tls.key-store-password=password %tls-registry.quarkus.oidc.tls.key-store-file= %tls-registry.quarkus.oidc.tls.key-store-password= -quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks +quarkus.native.additional-build-args=-H:IncludeResources=.*\\.p12 quarkus.http.cors=true quarkus.http.cors.origins=* diff --git a/integration-tests/oidc/src/main/resources/client-keystore.jks b/integration-tests/oidc/src/main/resources/client-keystore.jks deleted file mode 100644 index cf6d6ba454864d18322799afac37f520673193d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 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 literal 0 HcmV?d00001 diff --git a/integration-tests/oidc/src/main/resources/client-truststore.jks b/integration-tests/oidc/src/main/resources/client-truststore.jks deleted file mode 100644 index d89769a593721ee83cd324418457ec52817e82fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 937 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 diff --git a/integration-tests/oidc/src/main/resources/server-keystore.p12 b/integration-tests/oidc/src/main/resources/server-keystore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..6e476f513ef3069b1b9d8847effcbfe5095238d4 GIT binary patch literal 2712 zcma);c{mjO7RP6`%n+|_LWm-w3?gDe$sjX{VJsO^hLPQnB}+4gYOE15_9bf}l%?z; z`xX%yQe+)cmR`Bu=iaBD``5jH{Lb$=-|so+Ie&eggQUO`K|nB)0z1#fc{0KvVv8Hd z4$Pv!6d@Ft{DFJ|Nr685FAJIpp+Ff2a`M4TV1xgsiwgz>Wl^9y2ci~|_`3tjjkH3l z|4F%!@(=+_a?^l&GzRky$0$fj{h}w^CHo5mI>8G99z}Aoar}ECh!YAxim`FpM;HJd z!4Tj{2=CZg{ht`VghLgHS!XL@v;qhPGVrtrL3vYxdPEM^%rGuxU2F{U4H@B~V}5DE zt;J0BIrwD9_vo{g$DPZ5yiM2gk`7f=_MB^d@U1C!1wL3+`7%3hvf&X<(cyt%hTFxF|&uzBIqqhd4=Ol)EVETKXhEn^)UkbtRf%>lP`lUa^ zhFA674XYl1DcqI!-bow&BTs7|zP6DMCzxbAI?8`N&dMX2UhC^V9J3l-w0kG+3|_-r zM2?@lm}KD}wt1nhzyi5&^BUn<#f&h6M(V`Lp{kYkX0$mf*$bwKod)I58X~JD;EiUj zZi413y?yVC;$zsWcpH>lsw`n$@#fswDvx*iN-N4tn!DFF^&Z9F8@kMoDLX<;QN#_o4#8>bJk8{(-q zhPRemWGEsrIq5qo9S_6SVWbrn`(BHF#%pnnVj2m2#b0#QI}I<0(@Ik~SU`Zifv_wc zM6bWqGl{rbhU%K1;*-@ZTXU1wnqDm;?atZAmdbH2{ry>@AZBmrL`2Q9b(2qv3~KlL z-H@`}To-iJ^OVn4#~P1E`|69`jG0y$%FVtVfB*lz<9H3rE*Q2ZN{ z`VU8Aiao<U2%$J(t+M=^#;Wz~wbjTLatZ0l~G7g~t*mHWRMmmQrd@9|)ZIfO^%??=mye zvQuMW13An|YW265u-qNjdcIjg-mtJG85E7YO$k3QybwPUS;eoL9Joushu#k9c9-_= zzwj}hf-QAc(m{&;#wDkK(jjp!fDgbC;0^HmCn6>Pn1V_I5I!8q&GD$xS>>~5&M2uP zHI&tqkras6?;>`#EDFToKq7#EfP)YJ#{vE?Oa~8n+c2`?j#Pt$YvJ?y*tGRyp8pS~ zwL0~N&0q1K6g8uj`oP)HdTP0n_fdcAKJO}G|_Rd zDAGZRraBbGm$&HIb~R@^=rRpKPEN>REpTqMt$$y~I7o^^62-bp)~vJh4o{rah*aI& z{t|qG)0)~^!q4lD+-B>X<+ni>GFL>lODM~u%^lTkhF8ct<6=c8&pP!VJ*HVGVaC0s zwU)o91s+5PFY_uq-_6m}2E5oGfY0nVdj!c*FEKB}ZokEo?Q?-MHq6t}9hT^f+~t+-DjDpJ_LEM!vJMg_j^kC`{O z;}3Z39`cmc6daIk8XV#<>*e8najU}<^f+(wR{I-(t=oh(19z((9`PzOIWozi3*VoI zjYQ=Ip3+=xsc3n$n)^}SN}~7g@Ve)7#?&qUsi+MysWUCas38&J?qAR-@+m;}RAXRM z!SNR5j*SP?32q@yC1&lf(rDR*9?fgad^O)tqpDY{gwoDWkkHOq9#NaIkWKoFmhc3G zJQrFBYA8x#A74$)sO|j>&RaUf_|j*-CnX`pS|QK9Zz%8Q8Gdo@dUyC%ZSa${#YG`S z54qSQLW|9Jcq%!Tz=m_nc9A3SQ>TMDW!|aM=4M(Lc3J7OZ*4DFR>uxUEV>S-H&9;J z-+weqbCbOv}Pn)=Xl!=8cbMQjN=b03N{loJ_T^4CAOTfIM5ZkBQJmzRx>EI z1D2zhRdOQlEQ{7I(t~5)TrhOCBXi@G`B7(bE}{RjJ(Av zK{!Z{*5Jses)$298KtLz}a`Z`NFDB5-f6~#h$z?>tk2mTg r(6pTGb=TNS6F-FhTQ7)!_{}K}lhHeo`E|*eIZ$)GePKP$e-QI8L_6xT literal 0 HcmV?d00001 diff --git a/integration-tests/oidc/src/main/resources/server-truststore.jks b/integration-tests/oidc/src/main/resources/server-truststore.jks deleted file mode 100644 index 8ec8e126507b61e0e602b71d9f67b3d7e3c7cae3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 925 zcmezO_TO6u1_mY|W(3o$xs}0Ue&dE&8D>0B0 z=QXr6G&C?YG&M3ZGLI7HHL^4`0AdK2=Jq!+Dj|EBk(GhDiIJbdpox)-sfm%1VUF3s zo}9*=J}DCyv6d_>miqhX+0BER7vzr=weLB0SI~-EN4Tx;+oAX3sZu+AreC;W+rDov z-#5*V51%D8KX(20xaE+MMC5UqlCZ}&*8kY~jW=TIFRK(mcF!wJ^TT7)vd&p~1Uy)$ z`pW;@Uk~HZlKvCjci1|P$j&@8Uwi5_z2paTO5a=hS@rqWe0&n}OLbn_nKBuLad&4PoF%w@U!d89 z+LCrv&mDH_wWWUYJ`36D%^-U7h-9RXpo^<=ZFc0HX`&2UWMlL9lv%uVR}CpmJg{;8 zx_~1U?x&fU85tNCD;mff$O0oymXAe@MTGhL(rFKW9uTVSSw3Zn;O1#zMYmj$0}+^R zfPu)!z}8XzuI^>`BBZaSb80!?+r7pc#`09np_Lozt4zJ}{d+W?|#aPK1E8H9tc&p84 z9$uW3|Ks-G6^q-888qdu-^`O)?fLIsY>e9J3v8=pk6rP))@~NVdhf7^xr%i0hj$v? zKacmOG&aO636*{EblS9x!(U?iHAU{$t+Riy#c;{YELD{*se2PT}_AU?Y+Xxvefqh07{ov?f?J) diff --git a/integration-tests/oidc/src/main/resources/server-truststore.p12 b/integration-tests/oidc/src/main/resources/server-truststore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..d006d5d2dd43e1f3fb281ef9b0f70b62366d55d5 GIT binary patch literal 1238 zcmV;{1S$J4f&|h60Ru3C1bhYwDuzgg_YDCD0ic2eZ3Kb@X)uBWWiWySVFn2*hDe6@ z4FLxRpn?QaFoFb50s#Opf&@nf2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q02uTHfVto}cs>t6EcWV+4SL1JHTWWGObIPYjLO($cJE!p6hQ zyi+RQq~7$Nt0^uplY-(Qg*6&o;t56~ILD{z`oq)>kGQ3aU4F?EVOGuP(4I7CGNb9H zTH2%*k*_ys=fAZ@84#4HBY(?RIWiCfX0q#fiz*Anw@O0D*vt?B{pey7)3H8giqX*t zoT3ziOQh{2IPUSW&+Uuz@+3-;(^QJI$S#}t`=Q8Bq(7B{WFq;I#E+cwSBc3cnMtgM z?#Orx%n<2uc)~A>{`G;Hm5z2WwDhRNUv1I~NLz7O1$QD-+SDA{u#V^x2`|$mq8`?Z z0Vhkh8FpP0!X8ob)ZDYc1Yxe|{`ls#`zpl<9AIVdJYRhDlb3lzS=`jOFK4-f#;#u3 zjo|d+yzb#twx(gwwUccyB)!L<8gqo2X~a72Q{sQ796U~JBN*s1L&~w~hCBzj?F{!` zSf@7eSq=g#DIM2uufUIge<0RokWUH8USq;@uG4{?P}0dJQckA99BxrachA&KuYkzx<`~)CrtP z@{uVebFGAQVA7=e+T)#Jf&8gJFYOvL=w7`OQw=)}`)_7!%waE?$18Pf*VwKa`D^s; zz>m%2vdIz=g|l$x;9-eiLMoV|h)rGHr@F+9QhO;@xw+Zb@ge@w>mYRN#yqaMJ5S?e zyXyfY!7D;EfU#NAbonIFa{)S@=!;7THxuf^N5D>={aVO4Cw%D8-N>fNn6c*%CifI$g8ony^L za~KHBLb*MoAjFT3^Yh{U(Qm25G?a~UeD3INpyWw3B})@0^e7e0Ic2^c)g%Tfb;yurIc`ywk#<4b3FNC9O71OfpC00bafVWNS|wp1wX zyU_TEQ5xQ0(1XIrK!>`w9r$zbKD_q?6k{F#kOL?OfWpAH=<&{v*odT2lL7)K5Nth0 A8vp keycloak; +public class KeycloakXTestResourceLifecycleManager + implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static String KEYCLOAK_SERVER_URL; private static final String KEYCLOAK_REALM = "quarkus"; - private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-service-app"; - private static final String KEYCLOAK_VERSION = System.getProperty("keycloak.version", "23.0.1"); + private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-app"; - private static String CLIENT_KEYSTORE = "client-keystore.jks"; - private static String CLIENT_TRUSTSTORE = "client-truststore.jks"; + final KeycloakTestClient client = new KeycloakTestClient(new Tls()); - private static String SERVER_KEYSTORE = "server-keystore.jks"; - private static String SERVER_KEYSTORE_MOUNTED_PATH = "/etc/server-keystore.jks"; - private static String SERVER_TRUSTSTORE = "server-truststore.jks"; - private static String SERVER_TRUSTSTORE_MOUNTED_PATH = "/etc/server-truststore.jks"; - - @SuppressWarnings("resource") @Override public Map start() { - keycloak = new GenericContainer<>("quay.io/keycloak/keycloak:" + KEYCLOAK_VERSION) - .withExposedPorts(8080, 8443) - .withEnv("KEYCLOAK_ADMIN", "admin") - .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin") - .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1)); - - keycloak = keycloak - .withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY) - .withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY) - .withClasspathResourceMapping("/upconfig.json", "/opt/keycloak/upconfig.json", BindMode.READ_ONLY) - .withCommand("build --https-client-auth=required") - .withCommand(String.format( - "start --https-client-auth=required --hostname-strict=false" - + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password" - + " --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json", - SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH)); - keycloak.start(); - LOGGER.info(keycloak.getLogs()); - - KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443); RealmRepresentation realm = createRealm(KEYCLOAK_REALM); - postRealm(realm); - - return Map.of("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); - } + client.createRealm(realm); - private static void postRealm(RealmRepresentation realm) { - try { - createRequestSpec().auth().oauth2(getAdminAccessToken()) - .contentType("application/json") - .body(JsonSerialization.writeValueAsBytes(realm)) - .when() - .post(KEYCLOAK_SERVER_URL + "/admin/realms").then() - .statusCode(201); - } catch (IOException e) { - throw new RuntimeException(e); - } + return Map.of(); } private static RealmRepresentation createRealm(String name) { @@ -111,17 +62,6 @@ private static RealmRepresentation createRealm(String name) { return realm; } - private static String getAdminAccessToken() { - return createRequestSpec() - .param("grant_type", "password") - .param("username", "admin") - .param("password", "admin") - .param("client_id", "admin-cli") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); - } - private static ClientRepresentation createServiceClient(String clientId) { ClientRepresentation client = new ClientRepresentation(); @@ -155,39 +95,13 @@ private static UserRepresentation createUser(String username, List realm return user; } - public static String getAccessToken(String userName) { - return createRequestSpec().param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", KEYCLOAK_SERVICE_CLIENT) - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); - } - - public static String getRefreshToken(String userName) { - return createRequestSpec().param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", KEYCLOAK_SERVICE_CLIENT) - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getRefreshToken(); + @Override + public void setIntegrationTestContext(DevServicesContext context) { + client.setIntegrationTestContext(context); } @Override public void stop() { - createRequestSpec().auth().oauth2(getAdminAccessToken()) - .when() - .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); - - keycloak.stop(); } - private static RequestSpecification createRequestSpec() { - return RestAssured.given().trustStore(CLIENT_TRUSTSTORE, "password") - .keyStore(CLIENT_KEYSTORE, "password"); - } } diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java index b998d0bf7bc3b..3a75d88294dc4 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java @@ -1,7 +1,5 @@ package io.quarkus.it.keycloak; -import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken; - import java.net.URI; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -18,6 +16,8 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; import io.quarkus.websockets.BearerTokenClientEndpointConfigurator; @QuarkusTest @@ -27,6 +27,8 @@ public class WebsocketOidcTestCase { @TestHTTPResource("secured-hello") URI wsUri; + KeycloakTestClient client = new KeycloakTestClient(new Tls()); + @Test public void websocketTest() throws Exception { @@ -42,7 +44,7 @@ public void onMessage(String s) { }); session.getAsyncRemote().sendText("hello"); } - }, new BearerTokenClientEndpointConfigurator(getAccessToken("alice")), wsUri); + }, new BearerTokenClientEndpointConfigurator(client.getAccessToken("alice")), wsUri); try { Assertions.assertEquals("hello alice@gmail.com", message.poll(20, TimeUnit.SECONDS)); diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java index ba0f7171fc8f6..55d2b70561e22 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java @@ -28,20 +28,26 @@ public class KeycloakTestClient implements DevServicesContext.ContextAware { private final static String CLIENT_ID_PROP = "quarkus.oidc.client-id"; private final static String CLIENT_SECRET_PROP = "quarkus.oidc.credentials.secret"; - static { - RestAssured.useRelaxedHTTPSValidation(); - } - private DevServicesContext testContext; private final String authServerUrl; + private final Tls tls; public KeycloakTestClient() { - this(null); + this(null, null); + } + + public KeycloakTestClient(Tls tls) { + this(null, tls); } public KeycloakTestClient(String authServerUrl) { + this(authServerUrl, null); + } + + public KeycloakTestClient(String authServerUrl, Tls tls) { this.authServerUrl = authServerUrl; + this.tls = tls; } /** @@ -165,6 +171,55 @@ public String getAccessToken(String userName, String userSecret, String clientId return getAccessTokenInternal(userName, userSecret, clientId, clientSecret, scopes, getAuthServerUrl()); } + /** + * Get a refresh token from the default tenant realm using a password grant with a provided user name. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * User secret will be the same as the user name. + * Client id will be set to `quarkus-app` unless it has been configured with the `quarkus.oidc.client-id` property. + * Client secret will be to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` property. + */ + public String getRefreshToken(String userName) { + return getRefreshToken(userName, getClientId()); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name and client id. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * User secret will be the same as the user name. + * Client secret will be to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` property. + */ + public String getRefreshToken(String userName, String clientId) { + return getRefreshToken(userName, userName, clientId); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret and + * client id. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * Client secret will be set to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` propertys. + */ + public String getRefreshToken(String userName, String userSecret, String clientId) { + return getRefreshToken(userName, userSecret, clientId, getClientSecret()); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret, client + * id and secret. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + */ + public String getRefreshToken(String userName, String userSecret, String clientId, String clientSecret) { + return getRefreshToken(userName, userSecret, clientId, clientSecret, null); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret, client + * id and secret, and scopes. + */ + public String getRefreshToken(String userName, String userSecret, String clientId, String clientSecret, + List scopes) { + return getRefreshTokenInternal(userName, userSecret, clientId, clientSecret, scopes, getAuthServerUrl()); + } + /** * Get a realm access token using a password grant with a provided user name. * User secret will be the same as the user name. @@ -213,7 +268,17 @@ public String getRealmAccessToken(String realm, String userName, String userSecr private String getAccessTokenInternal(String userName, String userSecret, String clientId, String clientSecret, List scopes, String authServerUrl) { - RequestSpecification requestSpec = RestAssured.given().param("grant_type", "password") + return getAccessTokenResponse(userName, userSecret, clientId, clientSecret, scopes, authServerUrl).getToken(); + } + + private String getRefreshTokenInternal(String userName, String userSecret, String clientId, String clientSecret, + List scopes, String authServerUrl) { + return getAccessTokenResponse(userName, userSecret, clientId, clientSecret, scopes, authServerUrl).getRefreshToken(); + } + + private AccessTokenResponse getAccessTokenResponse(String userName, String userSecret, String clientId, String clientSecret, + List scopes, String authServerUrl) { + RequestSpecification requestSpec = getSpec().param("grant_type", "password") .param("username", userName) .param("password", userSecret) .param("client_id", clientId); @@ -224,12 +289,12 @@ private String getAccessTokenInternal(String userName, String userSecret, String requestSpec = requestSpec.param("scope", urlEncode(String.join(" ", scopes))); } return requestSpec.when().post(authServerUrl + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); + .as(AccessTokenResponse.class); } private String getClientAccessTokenInternal(String clientId, String clientSecret, List scopes, String authServerUrl) { - RequestSpecification requestSpec = RestAssured.given().param("grant_type", "client_credentials") + RequestSpecification requestSpec = getSpec().param("grant_type", "client_credentials") .param("client_id", clientId); if (clientSecret != null && !clientSecret.isBlank()) { requestSpec = requestSpec.param("client_secret", clientSecret); @@ -298,8 +363,7 @@ public String getAuthServerUrl() { */ public void createRealm(RealmRepresentation realm) { try { - RestAssured - .given() + getSpec() .auth().oauth2(getAdminAccessToken()) .contentType("application/json") .body(JsonSerialization.writeValueAsBytes(realm)) @@ -315,8 +379,7 @@ public void createRealm(RealmRepresentation realm) { * Delete a realm */ public void deleteRealm(String realm) { - RestAssured - .given() + getSpec() .auth().oauth2(getAdminAccessToken()) .when() .delete(getAuthServerBaseUrl() + "/admin/realms/" + realm).then().statusCode(204); @@ -375,4 +438,23 @@ private static String urlEncode(String value) { throw new RuntimeException(ex); } } + + private RequestSpecification getSpec() { + RequestSpecification spec = RestAssured.given(); + if (tls != null) { + spec = spec.keyStore(tls.keystore(), tls.keystorePassword()) + .trustStore(tls.truststore(), tls.truststorePassword()); + } else { + spec = spec.relaxedHTTPSValidation(); + } + return spec; + } + + public record Tls(String keystore, String keystorePassword, + String truststore, String truststorePassword) { + public Tls() { + this("client-keystore.p12", "password", "client-truststore.p12", "password"); + } + }; + }