keycloak-aplcache
Changes
examples/demo-template/README.md 9(+3 -6)
examples/demo-template/README.md.unconfigured 53(+37 -16)
examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSecretServlet.java 36(+0 -36)
examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductSAClientSignedJWTServlet.java 37(+0 -37)
examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java 61(+8 -53)
examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json 16(+0 -16)
examples/demo-template/testrealm.json 22(+5 -17)
Details
diff --git a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
index 1092701..d54a492 100755
--- a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
@@ -3,7 +3,14 @@
"resource" : "product-portal",
"auth-server-url" : "/auth",
"ssl-required" : "external",
- "credentials" : {
- "secret": "password"
+ "credentials": {
+ "jwt": {
+ "client-keystore-file": "classpath:keystore-client.jks",
+ "client-keystore-type": "JKS",
+ "client-keystore-password": "storepass",
+ "client-key-password": "keypass",
+ "client-key-alias": "clientkey",
+ "token-expiration": 10
+ }
}
}
examples/demo-template/README.md 9(+3 -6)
diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md
index 46211f9..52b3001 100755
--- a/examples/demo-template/README.md
+++ b/examples/demo-template/README.md
@@ -28,7 +28,7 @@ _This demo is meant to run on the same server instance as the Keycloak Server!_
Step 1: Make sure you've set up the Keycloak Server
--------------------------------------
-The Keycloak Appliance Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
+The Keycloak Demo Distribution comes with a preconfigured Keycloak server (based on Wildfly). You can use it out of
the box to run these demos. So, if you're using this, you can head to Step 2.
Alternatively, you can install the Keycloak Server onto any EAP 6.x, or Wildfly 8.x server, but there is
@@ -157,8 +157,8 @@ are still happening, but the auth-server knows you are already logged in so the
If you click on the logout link of either of the product or customer app, you'll be logged out of all the applications.
-If you click on [http://localhost:8080/customer-portal-js](http://localhost:8080/customer-portal-js) you can invoke
-on the pure HTML/Javascript application.
+The example also shows different methods of client authentication. The customer-portal example is using traditional authentication with client_id and client_secret,
+but the product-portal example is using client authentication with JWT signed by client private key, which is retrieved from the keystore file inside the product-portal WAR.
Step 6: Traditional OAuth2 Example
----------------------------------
@@ -240,9 +240,6 @@ An example for retrieve service account dedicated to the Client Application itse
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
-The example also shows different methods of client authentication. There is ProductSAClientSecretServlet using traditional authentication with clientId and client_secret,
-but there is also ProductSAClientSignedJWTServlet using client authentication with JWT signed by client private key.
-
Step 13: Offline Access Example
===============================
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
examples/demo-template/README.md.unconfigured 53(+37 -16)
diff --git a/examples/demo-template/README.md.unconfigured b/examples/demo-template/README.md.unconfigured
index a7d1893..d107720 100755
--- a/examples/demo-template/README.md.unconfigured
+++ b/examples/demo-template/README.md.unconfigured
@@ -44,9 +44,8 @@ You also need to add realm config to the same file. Add a new child-element to `
<profile>
....
- <subsystem xmlns="urn:jboss:domain:keycloak:1.0">
+ <subsystem xmlns="urn:jboss:domain:keycloak:1.1">
<realm name="demo">
- <realm-public-key>REALM PUBLIC KEY</realm-public-key>
<auth-server-url>KEYCLOAK URL</auth-server-url>
<ssl-required>external</ssl-required>
</realm>
@@ -55,7 +54,6 @@ You also need to add realm config to the same file. Add a new child-element to `
In the above snippet replace the following:
-* `REALM PUBLIC KEY` - replace with the public key for the realm. You can find this in the admin console by selecting the realm, then clicking on `Keys`
* `KEYCLOAK URL` - replace with the base url of Keycloak (for example http://localhost:8080/auth or http://keycloak.example.org/auth)
Don't start the WildFly server until you've configured and deployed the demo applications.
@@ -70,7 +68,7 @@ Run the following to deploy it:
# mvn install
# cp target/database.war <WILDFLY HOME>/standalone/deployments
-Next add the configuration for it to the Keycloak subsystem. Edit `<WILDFLY HOME>/standalone/configuration/standalone.xml` to `<subsystem xmlns="urn:jboss:domain:keycloak:1.0">` add:
+Next add the configuration for it to the Keycloak subsystem. Edit `<WILDFLY HOME>/standalone/configuration/standalone.xml` to `<subsystem xmlns="urn:jboss:domain:keycloak:1.1">` add:
<secure-deployment name="database.war">
<realm>demo</realm>
@@ -88,12 +86,17 @@ Run the following to deploy it:
# mvn install
# cp target/customer-portal.war <WILDFLY HOME>/standalone/deployments
-Then open the Keycloak admin console to add a configuration for it. Navigate to the realm and click on `Applications` then `Add Application`. Fill in the form with:
+Then open the Keycloak admin console to add a configuration for it. Navigate to the realm and click on `Clients` then `Add Client`. Fill in the form with:
-* Name - `customer-portal`
-* Redirect URI - `http://localhost:8080/customer-portal/*` (click `Add` after filling in the field)
+* Client ID - `customer-portal`
-Then click on `Save`. As it's a confidential (non-public) application you need the secret for it. Click on `Credentials` and note the value of the `Secret` field.
+Then click on `Save`. You will see more possibilities to setup client now, so you can add the following:
+`Access Type` - `confidential`
+`Valid Redirect URIs` - `http://localhost:8080/customer-portal/*` (click `Add` after filling in the field)
+
+Then click on `Save` again so that client is updated.
+
+As it's a confidential (non-public) application you need the secret for it. Click on `Credentials` and note the value of the `Secret` field.
Then edit `<WILDFLY HOME>/standalone/configuration/standalone.xml` and add the following to `<subsystem xmlns="urn:jboss:domain:keycloak:1.0">`:
@@ -117,21 +120,39 @@ Run the following to deploy it:
# mvn install
# cp target/product-portal.war <WILDFLY HOME>/standalone/deployments
-Then open the Keycloak admin console to add a configuration for it. Navigate to the realm and click on `Applications` then `Add Application`. Fill in the form with:
+Then open the Keycloak admin console to add a configuration for it. Navigate to the realm and click on `Clients` then `Add Client`. Fill in the form with:
+
+* Client ID - `product-portal`
+
+Then click on `Save`. You will see more possibilities to setup client now, so you can add the following:
+
+`Access Type` - `confidential`
+`Valid Redirect URIs` - `http://localhost:8080/product-portal/*` (click `Add` after filling in the field)
-* Name - `product-portal`
-* Redirect URI - `http://localhost:8080/product-portal/*` (click `Add` after filling in the field)
+Then click on `Save` again so that client is updated.
-Then click on `Save`. As it's a confidential (non-public) application you need the secret for it. Click on `Credentials` and note the value of the `Secret` field.
+It's a confidential (non-public) application, so we again need client credentials for it. But for product-portal, we will use authentication with signed JWT instead of traditional OAuth2 client secret.
+Click on `Credentials` and fill the following values:
+
+`Client Authenticator` - `Signed JWT`
+`Use JWKS URL` - `ON`
+`JWKS URL` - `/product-portal/k_jwks`
Then edit `<WILDFLY HOME>/standalone/configuration/standalone.xml` and add the following to `<subsystem xmlns="urn:jboss:domain:keycloak:1.0">`:
<secure-deployment name="product-portal.war">
<realm>demo</realm>
<resource>product-portal</resource>
- <credential name="secret">APPLICATION SECRET</credential>
+ <credential name="jwt">
+ <client-keystore-file>classpath:keystore-client.jks</client-keystore-file>
+ <client-keystore-type>JKS</client-keystore-type>
+ <client-keystore-password>storepass</client-keystore-password>
+ <client-key-password>keypass</client-key-password>
+ <client-key-alias>clientkey</client-key-alias>
+ <token-expiration>10</token-expiration>
+ </credential>
</secure-deployment>
-In the above snippet replace the following:
-
-* `APPLICATION SECRET` - replace with the applications secret you just noted from the Keycloak admin console
\ No newline at end of file
+With this configuration, the product-portal application will authenticate with JWT token signed by the private key from the file `keystore-client.jks`, which is available
+inside the application WAR. If you don't use `classpath:` prefix in the configuration, you can use any keystore file from filesystem. If you want to generate your own keystore file,
+you can either use `keytool` tool, but you can also generate the one inside Keycloak admin console and then save it locally.
\ No newline at end of file
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
index 78aeebe..a53bc8c 100644
--- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
@@ -33,6 +33,7 @@ import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.StreamUtil;
import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@@ -53,36 +54,28 @@ import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public abstract class ProductServiceAccountServlet extends HttpServlet {
+public class ProductServiceAccountServlet extends HttpServlet {
public static final String ERROR = "error";
public static final String TOKEN = "token";
public static final String TOKEN_PARSED = "idTokenParsed";
public static final String REFRESH_TOKEN = "refreshToken";
public static final String PRODUCTS = "products";
- public static final String CLIENT_AUTH_METHOD = "clientAuthMethod";
-
- protected abstract String getAdapterConfigLocation();
- protected abstract String getClientAuthenticationMethod();
public static String getLoginUrl(HttpServletRequest request) {
- return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/login";
- }
-
- public static String getRefreshUrl(HttpServletRequest request) {
- return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/refresh";
+ return "/service-account-portal/app/login";
}
public static String getLogoutUrl(HttpServletRequest request) {
- return "/service-account-portal/app-" + request.getAttribute(CLIENT_AUTH_METHOD) + "/logout";
+ return "/service-account-portal/app/logout";
}
@Override
public void init() throws ServletException {
- String adapterConfigLocation = getAdapterConfigLocation();
+ String adapterConfigLocation = "/WEB-INF/keycloak.json";
InputStream config = getServletContext().getResourceAsStream(adapterConfigLocation);
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
- getServletContext().setAttribute("deployment-" + getClientAuthenticationMethod(), deployment);
+ getServletContext().setAttribute(KeycloakDeployment.class.getName(), deployment);
HttpClient client = new DefaultHttpClient();
getServletContext().setAttribute(HttpClient.class.getName(), client);
@@ -95,13 +88,10 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- req.setAttribute(CLIENT_AUTH_METHOD, getClientAuthenticationMethod());
String reqUri = req.getRequestURI();
if (reqUri.endsWith("/login")) {
serviceAccountLogin(req);
- } else if (reqUri.endsWith("/refresh")) {
- refreshToken(req);
} else if (reqUri.endsWith("/logout")){
logout(req);
}
@@ -197,25 +187,6 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
}
}
- private void refreshToken(HttpServletRequest req) {
- KeycloakDeployment deployment = getKeycloakDeployment();
- String refreshToken = (String) req.getSession().getAttribute(REFRESH_TOKEN);
- if (refreshToken == null) {
- req.setAttribute(ERROR, "No refresh token available. Please login first");
- } else {
- try {
- AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
- setTokens(req, deployment, tokenResponse);
- } catch (ServerRequest.HttpFailure hfe) {
- hfe.printStackTrace();
- req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Status was: " + hfe.getStatus() + ", Error is: " + hfe.getError());
- } catch (Exception ioe) {
- ioe.printStackTrace();
- req.setAttribute(ERROR, "Failed refresh token. See server.log for details. Message is: " + ioe.getMessage());
- }
- }
- }
-
private void logout(HttpServletRequest req) {
KeycloakDeployment deployment = getKeycloakDeployment();
String refreshToken = (String) req.getSession().getAttribute(REFRESH_TOKEN);
@@ -240,27 +211,11 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
private String getContent(HttpEntity entity) throws IOException {
if (entity == null) return null;
InputStream is = entity.getContent();
- try {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- int c;
- while ((c = is.read()) != -1) {
- os.write(c);
- }
- byte[] bytes = os.toByteArray();
- String data = new String(bytes);
- return data;
- } finally {
- try {
- is.close();
- } catch (IOException ignored) {
-
- }
- }
-
+ return StreamUtil.readString(is);
}
private KeycloakDeployment getKeycloakDeployment() {
- return (KeycloakDeployment) getServletContext().getAttribute("deployment-" + getClientAuthenticationMethod());
+ return (KeycloakDeployment) getServletContext().getAttribute(KeycloakDeployment.class.getName());
}
private HttpClient getHttpClient() {
diff --git a/examples/demo-template/service-account/src/main/webapp/index.html b/examples/demo-template/service-account/src/main/webapp/index.html
index 1c03458..27528c4 100644
--- a/examples/demo-template/service-account/src/main/webapp/index.html
+++ b/examples/demo-template/service-account/src/main/webapp/index.html
@@ -16,11 +16,7 @@
-->
<html>
- <head><title>Service account example</title></head>
- <body>
- <ul>
- <li><a href="app-secret">Client authentication with client secret</a><br /><br /></li>
- <li><a href="app-jwt">Client authentication with JWT signed by client private key</a></li>
- </ul>
- </body>
+<head>
+ <meta http-equiv="Refresh" content="0; URL=app">
+</head>
</html>
\ No newline at end of file
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
index 5c76e63..fce6d5c 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/page.jsp
@@ -13,16 +13,12 @@
AccessToken token = (AccessToken) request.getSession().getAttribute(ProductServiceAccountServlet.TOKEN_PARSED);
String products = (String) request.getAttribute(ProductServiceAccountServlet.PRODUCTS);
String appError = (String) request.getAttribute(ProductServiceAccountServlet.ERROR);
- String clientAuthMethod = (String) request.getAttribute(ProductServiceAccountServlet.CLIENT_AUTH_METHOD);
String loginUrl = ProductServiceAccountServlet.getLoginUrl(request);
- String refreshUrl = ProductServiceAccountServlet.getRefreshUrl(request);
String logoutUrl = ProductServiceAccountServlet.getLogoutUrl(request);
%>
<h1>Service account portal</h1>
-<h2>Client authentication method: <%= clientAuthMethod %></h2>
-<p><a href="<%= loginUrl %>">Login</a> | <a href="<%= refreshUrl %>">Refresh token</a> | <a
- href="<%= logoutUrl %>">Logout</a></p>
+<p><a href="<%= loginUrl %>">Login</a> | <a href="<%= logoutUrl %>">Logout</a></p>
<hr />
<% if (appError != null) { %>
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
index eeb7ba5..0a47b8e 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/web.xml
@@ -25,22 +25,12 @@
<servlet>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
- <servlet-class>org.keycloak.example.ProductSAClientSecretServlet</servlet-class>
- </servlet>
-
- <servlet>
- <servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
- <servlet-class>org.keycloak.example.ProductSAClientSignedJWTServlet</servlet-class>
+ <servlet-class>org.keycloak.example.ProductServiceAccountServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProductSAClientSecretServlet</servlet-name>
- <url-pattern>/app-secret/*</url-pattern>
- </servlet-mapping>
-
- <servlet-mapping>
- <servlet-name>ProductSAClientSignedJWTServlet</servlet-name>
- <url-pattern>/app-jwt/*</url-pattern>
+ <url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
\ No newline at end of file
diff --git a/examples/demo-template/subsystem-config.xml b/examples/demo-template/subsystem-config.xml
index 1ba9211..99b5bc5 100755
--- a/examples/demo-template/subsystem-config.xml
+++ b/examples/demo-template/subsystem-config.xml
@@ -18,7 +18,6 @@
<!-- works with keycloak.json that comes with example -->
<subsystem xmlns="urn:jboss:domain:keycloak:1.0">
<realm name="demo">
- <realm-public-key>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</realm-public-key>
<auth-server-url>/auth</auth-server-url>
<ssl-required>external</ssl-required>
</realm>
@@ -30,7 +29,14 @@
<secure-deployment name="product-portal.war">
<realm>demo</realm>
<resource>product-portal</resource>
- <credential name="secret">password</credential>
+ <credential name="jwt">
+ <client-keystore-file>classpath:keystore-client.jks</client-keystore-file>
+ <client-keystore-type>JKS</client-keystore-type>
+ <client-keystore-password>storepass</client-keystore-password>
+ <client-key-password>keypass</client-key-password>
+ <client-key-alias>clientkey</client-key-alias>
+ <token-expiration>10</token-expiration>
+ </credential>
</secure-deployment>
<secure-deployment name="database.war">
<realm>demo</realm>
examples/demo-template/testrealm.json 22(+5 -17)
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 1917a02..7b9db36 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -79,13 +79,6 @@
"email" : "service-account-product-sa-client@placeholder.org",
"serviceAccountClientId": "product-sa-client",
"realmRoles": [ "user" ]
- },
- {
- "username" : "service-account-product-sa-client-jwt-auth",
- "enabled": true,
- "email" : "service-account-product-sa-client-jwt-auth@placeholder.org",
- "serviceAccountClientId": "product-sa-client-jwt-auth",
- "realmRoles": [ "user" ]
}
],
"roles" : {
@@ -175,7 +168,11 @@
"redirectUris": [
"/product-portal/*"
],
- "secret": "password"
+ "clientAuthenticatorType": "client-jwt",
+ "attributes": {
+ "use.jwks.url": "true",
+ "jwks.url": "/product-portal/k_jwks"
+ }
},
{
"clientId": "database-service",
@@ -208,15 +205,6 @@
"serviceAccountsEnabled": true
},
{
- "clientId": "product-sa-client-jwt-auth",
- "enabled": true,
- "serviceAccountsEnabled": true,
- "clientAuthenticatorType": "client-jwt",
- "attributes": {
- "jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
- }
- },
- {
"clientId": "offline-access-portal",
"enabled": true,
"consentRequired": true,
diff --git a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
index a788b19..8168832 100644
--- a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
@@ -36,6 +36,7 @@ import org.keycloak.protocol.oidc.utils.JWKSHttpUtils;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.util.CertificateInfoHelper;
+import org.keycloak.services.util.ResolveRelative;
import org.keycloak.util.JWKSUtils;
/**
@@ -59,6 +60,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientModel(client);
if (config.isUseJwksUrl()) {
String jwksUrl = config.getJwksUrl();
+ jwksUrl = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), jwksUrl);
JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
return JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
} else {