keycloak-scim/securing_apps/topics/oidc/java/installed-adapter.adoc

162 lines
No EOL
4.7 KiB
Text

[[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 renewal of stale tokens.
[[installed_adapter_installation]]
===== Adapter Installation
[source,xml,subs="attributes+"]
----
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-installed-adapter</artifactId>
<version>{project_versionMvn}</version>
</dependency>
----
===== 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,json]
----
{
"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,java]
----
// 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,java]
----
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);
});
}
}
----