164 lines
4.9 KiB
Text
164 lines
4.9 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`.
|
|
|
|
===== 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`.
|
|
|
|
TIP: The `KeycloakInstalled` adapter supports the `PKCE` [RFC 7636] mechanism to provide additional protection during
|
|
code to token exchanges in the `OIDC` protocol. PKCE can be enabled with the `"enable-pkce": true` setting
|
|
in the adapter configuration. Enabling PKCE is highly recommended, to avoid code injection and code replay attacks.
|
|
|
|
===== 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,
|
|
"enable-pkce": 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);
|
|
});
|
|
}
|
|
}
|
|
----
|