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.
Adapter Installation
<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 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
:
{
"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:
// 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.
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);
});
}
}