diff --git a/securing_apps/topics.adoc b/securing_apps/topics.adoc index 8b34ad5984..04faff4f75 100644 --- a/securing_apps/topics.adoc +++ b/securing_apps/topics.adoc @@ -28,6 +28,9 @@ ifeval::[{project_community}==true] include::topics/oidc/java/servlet-filter-adapter.adoc[] include::topics/oidc/java/jaas.adoc[] endif::[] +ifeval::[{project_community}==true] +include::topics/oidc/java/installed-adapter.adoc[] +endif::[] include::topics/oidc/java/adapter-context.adoc[] include::topics/oidc/java/adapter_error_handling.adoc[] include::topics/oidc/java/logout.adoc[] diff --git a/securing_apps/topics/oidc/java/installed-adapter.adoc b/securing_apps/topics/oidc/java/installed-adapter.adoc new file mode 100644 index 0000000000..00cf8eb946 --- /dev/null +++ b/securing_apps/topics/oidc/java/installed-adapter.adoc @@ -0,0 +1,162 @@ +[[_installed_adapter]] +==== CLI / Desktop Applications + +{project_name} supports securing desktop +(e.g. Swing, JavaFX) or CLI applications via the +`KeycloakInstalled` adapter by performing the authentication step via the system browser. + +The `KeycloakInstalled` adapter supports a `desktop` and a `manual` +variant. The desktop variant uses the system browser +to gather the user credentials. The manual variant +reads the user credentials from `STDIN`. + +Tip: Google provides some more information about this approach on at +https://developers.google.com/identity/protocols/OAuth2InstalledApp[OAuth2InstalledApp]. + +===== How it works + +To authenticate a user with the `desktop` variant the `KeycloakInstalled` +adapter opens a desktop browser window where a user uses the regular {project_name} +login pages to login when the `loginDesktop()` method is called on the `KeycloakInstalled` object. + +The login page URL is opened with redirect parameter +that points to a local `ServerSocket` listening on a free ephemeral port +on `localhost` which is started by the adapter. + +After a succesful login the `KeycloakInstalled` receives the authorization code +from the incoming HTTP request and performs the authorization code flow. +Once the code to token exchange is completed the `ServerSocket` is shutdown. + +TIP: If the user already has an active {project_name} session then +the login form is not shown but the code to token exchange is continued, +which enables a smooth Web based SSO experience. + +The client eventually receives the tokens (access_token, refresh_token, +id_token) which can then be used to call backend services. + +The `KeycloakInstalled` adapter provides support for reneval of stale tokens. + +[[_installed_adapter_installation]] +===== Adapter Installation + +[source,xml,subs="attributes+"] +---- + + + + org.keycloak + keycloak-installed-adapter + {project_versionMvn} + + +---- + + +===== Client Configuration + +The application needs to be configured as a `public` openid connect client with +`Standard Flow Enabled` and pass:[http://localhost:*] as an allowed `Valid Redirect URI`. + +===== Usage + +The `KeycloakInstalled` adapter reads it's configuration from +`META-INF/keycloak.json` on the classpath. Custom configurations +can be supplied with an `InputStream` or a `KeycloakDeployment` +through the `KeycloakInstalled` constructor. + +In the example below, the client configuration for `desktop-app` +uses the following `keycloak.json`: + +[source] +---- + +{ + "realm": "desktop-app-auth", + "auth-server-url": "http://localhost:8081/auth", + "ssl-required": "external", + "resource": "desktop-app", + "public-client": true, + "use-resource-role-mappings": true +} + +---- + +the following sketch demonstrates working with the `KeycloakInstalled` adapter: +[source] +---- + +// reads the configuration from classpath: META-INF/keycloak.json +KeycloakInstalled keycloak = new KeycloakInstalled(); + +// opens desktop browser +keycloak.loginDesktop(); + +AccessToken token = keycloak.getToken(); +// use token to send backend request + +// ensure token is valid for at least 30 seconds +long minValidity = 30L; +String tokenString = keycloak.getTokenString(minValidity, TimeUnit.SECONDS); + + + // when you want to logout the user. +keycloak.logout(); + +---- + +TIP: The `KeycloakInstalled` class supports customization of the http responses returned by +login / logout requests via the `loginResponseWriter` and `logoutResponseWriter` attributes. + +===== Example + +The following provides an example for the configuration mentioned above. + +[source] +---- +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.keycloak.adapters.installed.KeycloakInstalled; +import org.keycloak.representations.AccessToken; + +public class DesktopApp { + + public static void main(String[] args) throws Exception { + + KeycloakInstalled keycloak = new KeycloakInstalled(); + keycloak.setLocale(Locale.ENGLISH); + keycloak.loginDesktop(); + + AccessToken token = keycloak.getToken(); + Executors.newSingleThreadExecutor().submit(() -> { + + System.out.println("Logged in..."); + System.out.println("Token: " + token.getSubject()); + System.out.println("Username: " + token.getPreferredUsername()); + try { + System.out.println("AccessToken: " + keycloak.getTokenString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + + int timeoutSeconds = 20; + System.out.printf("Logging out in...%d Seconds%n", timeoutSeconds); + try { + TimeUnit.SECONDS.sleep(timeoutSeconds); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + keycloak.logout(); + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("Exiting..."); + System.exit(0); + }); + } +} +---- \ No newline at end of file